##// END OF EJS Templates
typing: explicitly type some `mercurial.util` eol code to avoid @overload...
Matt Harbison -
r52614:ed280858 default
parent child Browse files
Show More
@@ -1,3359 +1,3378 b''
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
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 """Mercurial utility functions and platform specific implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16
16
17 import abc
17 import abc
18 import collections
18 import collections
19 import contextlib
19 import contextlib
20 import errno
20 import errno
21 import gc
21 import gc
22 import hashlib
22 import hashlib
23 import io
23 import io
24 import itertools
24 import itertools
25 import locale
25 import locale
26 import mmap
26 import mmap
27 import os
27 import os
28 import pickle # provides util.pickle symbol
28 import pickle # provides util.pickle symbol
29 import re as remod
29 import re as remod
30 import shutil
30 import shutil
31 import stat
31 import stat
32 import sys
32 import sys
33 import time
33 import time
34 import traceback
34 import traceback
35 import typing
35 import warnings
36 import warnings
36
37
37 from typing import (
38 from typing import (
38 Any,
39 Any,
40 BinaryIO,
41 Callable,
39 Iterable,
42 Iterable,
40 Iterator,
43 Iterator,
41 List,
44 List,
42 Optional,
45 Optional,
43 Tuple,
46 Tuple,
44 )
47 )
45
48
46 from .node import hex
49 from .node import hex
47 from .thirdparty import attr
50 from .thirdparty import attr
48 from .pycompat import (
51 from .pycompat import (
49 open,
52 open,
50 )
53 )
51 from hgdemandimport import tracing
54 from hgdemandimport import tracing
52 from . import (
55 from . import (
53 encoding,
56 encoding,
54 error,
57 error,
55 i18n,
58 i18n,
56 policy,
59 policy,
57 pycompat,
60 pycompat,
61 typelib,
58 urllibcompat,
62 urllibcompat,
59 )
63 )
60 from .utils import (
64 from .utils import (
61 compression,
65 compression,
62 hashutil,
66 hashutil,
63 procutil,
67 procutil,
64 stringutil,
68 stringutil,
65 )
69 )
66
70
67 # keeps pyflakes happy
71 # keeps pyflakes happy
68 assert [
72 assert [
69 Iterable,
73 Iterable,
70 Iterator,
74 Iterator,
71 List,
75 List,
72 Optional,
76 Optional,
73 Tuple,
77 Tuple,
74 ]
78 ]
75
79
76
80
77 base85 = policy.importmod('base85')
81 base85 = policy.importmod('base85')
78 osutil = policy.importmod('osutil')
82 osutil = policy.importmod('osutil')
79
83
80 b85decode = base85.b85decode
84 b85decode = base85.b85decode
81 b85encode = base85.b85encode
85 b85encode = base85.b85encode
82
86
83 cookielib = pycompat.cookielib
87 cookielib = pycompat.cookielib
84 httplib = pycompat.httplib
88 httplib = pycompat.httplib
85 safehasattr = pycompat.safehasattr
89 safehasattr = pycompat.safehasattr
86 socketserver = pycompat.socketserver
90 socketserver = pycompat.socketserver
87 bytesio = io.BytesIO
91 bytesio = io.BytesIO
88 # TODO deprecate stringio name, as it is a lie on Python 3.
92 # TODO deprecate stringio name, as it is a lie on Python 3.
89 stringio = bytesio
93 stringio = bytesio
90 xmlrpclib = pycompat.xmlrpclib
94 xmlrpclib = pycompat.xmlrpclib
91
95
92 httpserver = urllibcompat.httpserver
96 httpserver = urllibcompat.httpserver
93 urlerr = urllibcompat.urlerr
97 urlerr = urllibcompat.urlerr
94 urlreq = urllibcompat.urlreq
98 urlreq = urllibcompat.urlreq
95
99
96 # workaround for win32mbcs
100 # workaround for win32mbcs
97 _filenamebytestr = pycompat.bytestr
101 _filenamebytestr = pycompat.bytestr
98
102
99 if pycompat.iswindows:
103 if pycompat.iswindows:
100 from . import windows as platform
104 from . import windows as platform
101 else:
105 else:
102 from . import posix as platform
106 from . import posix as platform
103
107
104 _ = i18n._
108 _ = i18n._
105
109
106 abspath = platform.abspath
110 abspath = platform.abspath
107 bindunixsocket = platform.bindunixsocket
111 bindunixsocket = platform.bindunixsocket
108 cachestat = platform.cachestat
112 cachestat = platform.cachestat
109 checkexec = platform.checkexec
113 checkexec = platform.checkexec
110 checklink = platform.checklink
114 checklink = platform.checklink
111 copymode = platform.copymode
115 copymode = platform.copymode
112 expandglobs = platform.expandglobs
116 expandglobs = platform.expandglobs
113 getfsmountpoint = platform.getfsmountpoint
117 getfsmountpoint = platform.getfsmountpoint
114 getfstype = platform.getfstype
118 getfstype = platform.getfstype
115 get_password = platform.get_password
119 get_password = platform.get_password
116 groupmembers = platform.groupmembers
120 groupmembers = platform.groupmembers
117 groupname = platform.groupname
121 groupname = platform.groupname
118 isexec = platform.isexec
122 isexec = platform.isexec
119 isowner = platform.isowner
123 isowner = platform.isowner
120 listdir = osutil.listdir
124 listdir = osutil.listdir
121 localpath = platform.localpath
125 localpath = platform.localpath
122 lookupreg = platform.lookupreg
126 lookupreg = platform.lookupreg
123 makedir = platform.makedir
127 makedir = platform.makedir
124 nlinks = platform.nlinks
128 nlinks = platform.nlinks
125 normpath = platform.normpath
129 normpath = platform.normpath
126 normcase = platform.normcase
130 normcase = platform.normcase
127 normcasespec = platform.normcasespec
131 normcasespec = platform.normcasespec
128 normcasefallback = platform.normcasefallback
132 normcasefallback = platform.normcasefallback
129 openhardlinks = platform.openhardlinks
133 openhardlinks = platform.openhardlinks
130 oslink = platform.oslink
134 oslink = platform.oslink
131 parsepatchoutput = platform.parsepatchoutput
135 parsepatchoutput = platform.parsepatchoutput
132 pconvert = platform.pconvert
136 pconvert = platform.pconvert
133 poll = platform.poll
137 poll = platform.poll
134 posixfile = platform.posixfile
138 posixfile = platform.posixfile
135 readlink = platform.readlink
139 readlink = platform.readlink
136 rename = platform.rename
140 rename = platform.rename
137 removedirs = platform.removedirs
141 removedirs = platform.removedirs
138 samedevice = platform.samedevice
142 samedevice = platform.samedevice
139 samefile = platform.samefile
143 samefile = platform.samefile
140 samestat = platform.samestat
144 samestat = platform.samestat
141 setflags = platform.setflags
145 setflags = platform.setflags
142 split = platform.split
146 split = platform.split
143 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
147 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
144 statisexec = platform.statisexec
148 statisexec = platform.statisexec
145 statislink = platform.statislink
149 statislink = platform.statislink
146 umask = platform.umask
150 umask = platform.umask
147 unlink = platform.unlink
151 unlink = platform.unlink
148 username = platform.username
152 username = platform.username
149
153
150
154
151 def setumask(val: int) -> None:
155 def setumask(val: int) -> None:
152 '''updates the umask. used by chg server'''
156 '''updates the umask. used by chg server'''
153 if pycompat.iswindows:
157 if pycompat.iswindows:
154 return
158 return
155 os.umask(val)
159 os.umask(val)
156 global umask
160 global umask
157 platform.umask = umask = val & 0o777
161 platform.umask = umask = val & 0o777
158
162
159
163
160 # small compat layer
164 # small compat layer
161 compengines = compression.compengines
165 compengines = compression.compengines
162 SERVERROLE = compression.SERVERROLE
166 SERVERROLE = compression.SERVERROLE
163 CLIENTROLE = compression.CLIENTROLE
167 CLIENTROLE = compression.CLIENTROLE
164
168
165 # Python compatibility
169 # Python compatibility
166
170
167 _notset = object()
171 _notset = object()
168
172
169
173
170 def bitsfrom(container):
174 def bitsfrom(container):
171 bits = 0
175 bits = 0
172 for bit in container:
176 for bit in container:
173 bits |= bit
177 bits |= bit
174 return bits
178 return bits
175
179
176
180
177 # python 2.6 still have deprecation warning enabled by default. We do not want
181 # python 2.6 still have deprecation warning enabled by default. We do not want
178 # to display anything to standard user so detect if we are running test and
182 # to display anything to standard user so detect if we are running test and
179 # only use python deprecation warning in this case.
183 # only use python deprecation warning in this case.
180 _dowarn = bool(encoding.environ.get(b'HGEMITWARNINGS'))
184 _dowarn = bool(encoding.environ.get(b'HGEMITWARNINGS'))
181 if _dowarn:
185 if _dowarn:
182 # explicitly unfilter our warning for python 2.7
186 # explicitly unfilter our warning for python 2.7
183 #
187 #
184 # The option of setting PYTHONWARNINGS in the test runner was investigated.
188 # The option of setting PYTHONWARNINGS in the test runner was investigated.
185 # However, module name set through PYTHONWARNINGS was exactly matched, so
189 # However, module name set through PYTHONWARNINGS was exactly matched, so
186 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
190 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
187 # makes the whole PYTHONWARNINGS thing useless for our usecase.
191 # makes the whole PYTHONWARNINGS thing useless for our usecase.
188 warnings.filterwarnings('default', '', DeprecationWarning, 'mercurial')
192 warnings.filterwarnings('default', '', DeprecationWarning, 'mercurial')
189 warnings.filterwarnings('default', '', DeprecationWarning, 'hgext')
193 warnings.filterwarnings('default', '', DeprecationWarning, 'hgext')
190 warnings.filterwarnings('default', '', DeprecationWarning, 'hgext3rd')
194 warnings.filterwarnings('default', '', DeprecationWarning, 'hgext3rd')
191 if _dowarn:
195 if _dowarn:
192 # silence warning emitted by passing user string to re.sub()
196 # silence warning emitted by passing user string to re.sub()
193 warnings.filterwarnings(
197 warnings.filterwarnings(
194 'ignore', 'bad escape', DeprecationWarning, 'mercurial'
198 'ignore', 'bad escape', DeprecationWarning, 'mercurial'
195 )
199 )
196 warnings.filterwarnings(
200 warnings.filterwarnings(
197 'ignore', 'invalid escape sequence', DeprecationWarning, 'mercurial'
201 'ignore', 'invalid escape sequence', DeprecationWarning, 'mercurial'
198 )
202 )
199 # TODO: reinvent imp.is_frozen()
203 # TODO: reinvent imp.is_frozen()
200 warnings.filterwarnings(
204 warnings.filterwarnings(
201 'ignore',
205 'ignore',
202 'the imp module is deprecated',
206 'the imp module is deprecated',
203 DeprecationWarning,
207 DeprecationWarning,
204 'mercurial',
208 'mercurial',
205 )
209 )
206
210
207
211
208 def nouideprecwarn(msg, version, stacklevel=1):
212 def nouideprecwarn(msg, version, stacklevel=1):
209 """Issue an python native deprecation warning
213 """Issue an python native deprecation warning
210
214
211 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
215 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
212 """
216 """
213 if _dowarn:
217 if _dowarn:
214 msg += (
218 msg += (
215 b"\n(compatibility will be dropped after Mercurial-%s,"
219 b"\n(compatibility will be dropped after Mercurial-%s,"
216 b" update your code.)"
220 b" update your code.)"
217 ) % version
221 ) % version
218 warnings.warn(pycompat.sysstr(msg), DeprecationWarning, stacklevel + 1)
222 warnings.warn(pycompat.sysstr(msg), DeprecationWarning, stacklevel + 1)
219 # on python 3 with chg, we will need to explicitly flush the output
223 # on python 3 with chg, we will need to explicitly flush the output
220 sys.stderr.flush()
224 sys.stderr.flush()
221
225
222
226
223 DIGESTS = {
227 DIGESTS = {
224 b'md5': hashlib.md5,
228 b'md5': hashlib.md5,
225 b'sha1': hashutil.sha1,
229 b'sha1': hashutil.sha1,
226 b'sha512': hashlib.sha512,
230 b'sha512': hashlib.sha512,
227 }
231 }
228 # List of digest types from strongest to weakest
232 # List of digest types from strongest to weakest
229 DIGESTS_BY_STRENGTH = [b'sha512', b'sha1', b'md5']
233 DIGESTS_BY_STRENGTH = [b'sha512', b'sha1', b'md5']
230
234
231 for k in DIGESTS_BY_STRENGTH:
235 for k in DIGESTS_BY_STRENGTH:
232 assert k in DIGESTS
236 assert k in DIGESTS
233
237
234
238
235 class digester:
239 class digester:
236 """helper to compute digests.
240 """helper to compute digests.
237
241
238 This helper can be used to compute one or more digests given their name.
242 This helper can be used to compute one or more digests given their name.
239
243
240 >>> d = digester([b'md5', b'sha1'])
244 >>> d = digester([b'md5', b'sha1'])
241 >>> d.update(b'foo')
245 >>> d.update(b'foo')
242 >>> [k for k in sorted(d)]
246 >>> [k for k in sorted(d)]
243 ['md5', 'sha1']
247 ['md5', 'sha1']
244 >>> d[b'md5']
248 >>> d[b'md5']
245 'acbd18db4cc2f85cedef654fccc4a4d8'
249 'acbd18db4cc2f85cedef654fccc4a4d8'
246 >>> d[b'sha1']
250 >>> d[b'sha1']
247 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
251 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
248 >>> digester.preferred([b'md5', b'sha1'])
252 >>> digester.preferred([b'md5', b'sha1'])
249 'sha1'
253 'sha1'
250 """
254 """
251
255
252 def __init__(self, digests, s=b''):
256 def __init__(self, digests, s=b''):
253 self._hashes = {}
257 self._hashes = {}
254 for k in digests:
258 for k in digests:
255 if k not in DIGESTS:
259 if k not in DIGESTS:
256 raise error.Abort(_(b'unknown digest type: %s') % k)
260 raise error.Abort(_(b'unknown digest type: %s') % k)
257 self._hashes[k] = DIGESTS[k]()
261 self._hashes[k] = DIGESTS[k]()
258 if s:
262 if s:
259 self.update(s)
263 self.update(s)
260
264
261 def update(self, data):
265 def update(self, data):
262 for h in self._hashes.values():
266 for h in self._hashes.values():
263 h.update(data)
267 h.update(data)
264
268
265 def __getitem__(self, key):
269 def __getitem__(self, key):
266 if key not in DIGESTS:
270 if key not in DIGESTS:
267 raise error.Abort(_(b'unknown digest type: %s') % k)
271 raise error.Abort(_(b'unknown digest type: %s') % k)
268 return hex(self._hashes[key].digest())
272 return hex(self._hashes[key].digest())
269
273
270 def __iter__(self):
274 def __iter__(self):
271 return iter(self._hashes)
275 return iter(self._hashes)
272
276
273 @staticmethod
277 @staticmethod
274 def preferred(supported):
278 def preferred(supported):
275 """returns the strongest digest type in both supported and DIGESTS."""
279 """returns the strongest digest type in both supported and DIGESTS."""
276
280
277 for k in DIGESTS_BY_STRENGTH:
281 for k in DIGESTS_BY_STRENGTH:
278 if k in supported:
282 if k in supported:
279 return k
283 return k
280 return None
284 return None
281
285
282
286
283 class digestchecker:
287 class digestchecker:
284 """file handle wrapper that additionally checks content against a given
288 """file handle wrapper that additionally checks content against a given
285 size and digests.
289 size and digests.
286
290
287 d = digestchecker(fh, size, {'md5': '...'})
291 d = digestchecker(fh, size, {'md5': '...'})
288
292
289 When multiple digests are given, all of them are validated.
293 When multiple digests are given, all of them are validated.
290 """
294 """
291
295
292 def __init__(self, fh, size, digests):
296 def __init__(self, fh, size, digests):
293 self._fh = fh
297 self._fh = fh
294 self._size = size
298 self._size = size
295 self._got = 0
299 self._got = 0
296 self._digests = dict(digests)
300 self._digests = dict(digests)
297 self._digester = digester(self._digests.keys())
301 self._digester = digester(self._digests.keys())
298
302
299 def read(self, length=-1):
303 def read(self, length=-1):
300 content = self._fh.read(length)
304 content = self._fh.read(length)
301 self._digester.update(content)
305 self._digester.update(content)
302 self._got += len(content)
306 self._got += len(content)
303 return content
307 return content
304
308
305 def validate(self):
309 def validate(self):
306 if self._size != self._got:
310 if self._size != self._got:
307 raise error.Abort(
311 raise error.Abort(
308 _(b'size mismatch: expected %d, got %d')
312 _(b'size mismatch: expected %d, got %d')
309 % (self._size, self._got)
313 % (self._size, self._got)
310 )
314 )
311 for k, v in self._digests.items():
315 for k, v in self._digests.items():
312 if v != self._digester[k]:
316 if v != self._digester[k]:
313 # i18n: first parameter is a digest name
317 # i18n: first parameter is a digest name
314 raise error.Abort(
318 raise error.Abort(
315 _(b'%s mismatch: expected %s, got %s')
319 _(b'%s mismatch: expected %s, got %s')
316 % (k, v, self._digester[k])
320 % (k, v, self._digester[k])
317 )
321 )
318
322
319
323
320 try:
324 try:
321 buffer = buffer # pytype: disable=name-error
325 buffer = buffer # pytype: disable=name-error
322 except NameError:
326 except NameError:
323
327
324 def buffer(sliceable, offset=0, length=None):
328 def buffer(sliceable, offset=0, length=None):
325 if length is not None:
329 if length is not None:
326 return memoryview(sliceable)[offset : offset + length]
330 return memoryview(sliceable)[offset : offset + length]
327 return memoryview(sliceable)[offset:]
331 return memoryview(sliceable)[offset:]
328
332
329
333
330 _chunksize = 4096
334 _chunksize = 4096
331
335
332
336
333 class bufferedinputpipe:
337 class bufferedinputpipe:
334 """a manually buffered input pipe
338 """a manually buffered input pipe
335
339
336 Python will not let us use buffered IO and lazy reading with 'polling' at
340 Python will not let us use buffered IO and lazy reading with 'polling' at
337 the same time. We cannot probe the buffer state and select will not detect
341 the same time. We cannot probe the buffer state and select will not detect
338 that data are ready to read if they are already buffered.
342 that data are ready to read if they are already buffered.
339
343
340 This class let us work around that by implementing its own buffering
344 This class let us work around that by implementing its own buffering
341 (allowing efficient readline) while offering a way to know if the buffer is
345 (allowing efficient readline) while offering a way to know if the buffer is
342 empty from the output (allowing collaboration of the buffer with polling).
346 empty from the output (allowing collaboration of the buffer with polling).
343
347
344 This class lives in the 'util' module because it makes use of the 'os'
348 This class lives in the 'util' module because it makes use of the 'os'
345 module from the python stdlib.
349 module from the python stdlib.
346 """
350 """
347
351
348 def __new__(cls, fh):
352 def __new__(cls, fh):
349 # If we receive a fileobjectproxy, we need to use a variation of this
353 # If we receive a fileobjectproxy, we need to use a variation of this
350 # class that notifies observers about activity.
354 # class that notifies observers about activity.
351 if isinstance(fh, fileobjectproxy):
355 if isinstance(fh, fileobjectproxy):
352 cls = observedbufferedinputpipe
356 cls = observedbufferedinputpipe
353
357
354 return super(bufferedinputpipe, cls).__new__(cls)
358 return super(bufferedinputpipe, cls).__new__(cls)
355
359
356 def __init__(self, input):
360 def __init__(self, input):
357 self._input = input
361 self._input = input
358 self._buffer = []
362 self._buffer = []
359 self._eof = False
363 self._eof = False
360 self._lenbuf = 0
364 self._lenbuf = 0
361
365
362 @property
366 @property
363 def hasbuffer(self):
367 def hasbuffer(self):
364 """True is any data is currently buffered
368 """True is any data is currently buffered
365
369
366 This will be used externally a pre-step for polling IO. If there is
370 This will be used externally a pre-step for polling IO. If there is
367 already data then no polling should be set in place."""
371 already data then no polling should be set in place."""
368 return bool(self._buffer)
372 return bool(self._buffer)
369
373
370 @property
374 @property
371 def closed(self):
375 def closed(self):
372 return self._input.closed
376 return self._input.closed
373
377
374 def fileno(self):
378 def fileno(self):
375 return self._input.fileno()
379 return self._input.fileno()
376
380
377 def close(self):
381 def close(self):
378 return self._input.close()
382 return self._input.close()
379
383
380 def read(self, size):
384 def read(self, size):
381 while (not self._eof) and (self._lenbuf < size):
385 while (not self._eof) and (self._lenbuf < size):
382 self._fillbuffer()
386 self._fillbuffer()
383 return self._frombuffer(size)
387 return self._frombuffer(size)
384
388
385 def unbufferedread(self, size):
389 def unbufferedread(self, size):
386 if not self._eof and self._lenbuf == 0:
390 if not self._eof and self._lenbuf == 0:
387 self._fillbuffer(max(size, _chunksize))
391 self._fillbuffer(max(size, _chunksize))
388 return self._frombuffer(min(self._lenbuf, size))
392 return self._frombuffer(min(self._lenbuf, size))
389
393
390 def readline(self, *args, **kwargs):
394 def readline(self, *args, **kwargs):
391 if len(self._buffer) > 1:
395 if len(self._buffer) > 1:
392 # this should not happen because both read and readline end with a
396 # this should not happen because both read and readline end with a
393 # _frombuffer call that collapse it.
397 # _frombuffer call that collapse it.
394 self._buffer = [b''.join(self._buffer)]
398 self._buffer = [b''.join(self._buffer)]
395 self._lenbuf = len(self._buffer[0])
399 self._lenbuf = len(self._buffer[0])
396 lfi = -1
400 lfi = -1
397 if self._buffer:
401 if self._buffer:
398 lfi = self._buffer[-1].find(b'\n')
402 lfi = self._buffer[-1].find(b'\n')
399 while (not self._eof) and lfi < 0:
403 while (not self._eof) and lfi < 0:
400 self._fillbuffer()
404 self._fillbuffer()
401 if self._buffer:
405 if self._buffer:
402 lfi = self._buffer[-1].find(b'\n')
406 lfi = self._buffer[-1].find(b'\n')
403 size = lfi + 1
407 size = lfi + 1
404 if lfi < 0: # end of file
408 if lfi < 0: # end of file
405 size = self._lenbuf
409 size = self._lenbuf
406 elif len(self._buffer) > 1:
410 elif len(self._buffer) > 1:
407 # we need to take previous chunks into account
411 # we need to take previous chunks into account
408 size += self._lenbuf - len(self._buffer[-1])
412 size += self._lenbuf - len(self._buffer[-1])
409 return self._frombuffer(size)
413 return self._frombuffer(size)
410
414
411 def _frombuffer(self, size):
415 def _frombuffer(self, size):
412 """return at most 'size' data from the buffer
416 """return at most 'size' data from the buffer
413
417
414 The data are removed from the buffer."""
418 The data are removed from the buffer."""
415 if size == 0 or not self._buffer:
419 if size == 0 or not self._buffer:
416 return b''
420 return b''
417 buf = self._buffer[0]
421 buf = self._buffer[0]
418 if len(self._buffer) > 1:
422 if len(self._buffer) > 1:
419 buf = b''.join(self._buffer)
423 buf = b''.join(self._buffer)
420
424
421 data = buf[:size]
425 data = buf[:size]
422 buf = buf[len(data) :]
426 buf = buf[len(data) :]
423 if buf:
427 if buf:
424 self._buffer = [buf]
428 self._buffer = [buf]
425 self._lenbuf = len(buf)
429 self._lenbuf = len(buf)
426 else:
430 else:
427 self._buffer = []
431 self._buffer = []
428 self._lenbuf = 0
432 self._lenbuf = 0
429 return data
433 return data
430
434
431 def _fillbuffer(self, size=_chunksize):
435 def _fillbuffer(self, size=_chunksize):
432 """read data to the buffer"""
436 """read data to the buffer"""
433 data = os.read(self._input.fileno(), size)
437 data = os.read(self._input.fileno(), size)
434 if not data:
438 if not data:
435 self._eof = True
439 self._eof = True
436 else:
440 else:
437 self._lenbuf += len(data)
441 self._lenbuf += len(data)
438 self._buffer.append(data)
442 self._buffer.append(data)
439
443
440 return data
444 return data
441
445
442
446
443 def has_mmap_populate():
447 def has_mmap_populate():
444 return hasattr(mmap, 'MAP_POPULATE')
448 return hasattr(mmap, 'MAP_POPULATE')
445
449
446
450
447 def mmapread(fp, size=None, pre_populate=True):
451 def mmapread(fp, size=None, pre_populate=True):
448 """Read a file content using mmap
452 """Read a file content using mmap
449
453
450 The responsability of checking the file system is mmap safe is the
454 The responsability of checking the file system is mmap safe is the
451 responsability of the caller (see `vfs.is_mmap_safe`).
455 responsability of the caller (see `vfs.is_mmap_safe`).
452
456
453 In some case, a normal string might be returned.
457 In some case, a normal string might be returned.
454
458
455 If `pre_populate` is True (the default), the mmapped data will be
459 If `pre_populate` is True (the default), the mmapped data will be
456 pre-populated in memory if the system support this option, this slow down
460 pre-populated in memory if the system support this option, this slow down
457 the initial mmaping but avoid potentially crippling page fault on later
461 the initial mmaping but avoid potentially crippling page fault on later
458 access. If this is not the desired behavior, set `pre_populate` to False.
462 access. If this is not the desired behavior, set `pre_populate` to False.
459 """
463 """
460 if size == 0:
464 if size == 0:
461 # size of 0 to mmap.mmap() means "all data"
465 # size of 0 to mmap.mmap() means "all data"
462 # rather than "zero bytes", so special case that.
466 # rather than "zero bytes", so special case that.
463 return b''
467 return b''
464 elif size is None:
468 elif size is None:
465 size = 0
469 size = 0
466 fd = getattr(fp, 'fileno', lambda: fp)()
470 fd = getattr(fp, 'fileno', lambda: fp)()
467 flags = mmap.MAP_PRIVATE
471 flags = mmap.MAP_PRIVATE
468 if pre_populate:
472 if pre_populate:
469 flags |= getattr(mmap, 'MAP_POPULATE', 0)
473 flags |= getattr(mmap, 'MAP_POPULATE', 0)
470 try:
474 try:
471 m = mmap.mmap(fd, size, flags=flags, prot=mmap.PROT_READ)
475 m = mmap.mmap(fd, size, flags=flags, prot=mmap.PROT_READ)
472 return m
476 return m
473 except ValueError:
477 except ValueError:
474 # Empty files cannot be mmapped, but mmapread should still work. Check
478 # Empty files cannot be mmapped, but mmapread should still work. Check
475 # if the file is empty, and if so, return an empty buffer.
479 # if the file is empty, and if so, return an empty buffer.
476 if os.fstat(fd).st_size == 0:
480 if os.fstat(fd).st_size == 0:
477 return b''
481 return b''
478 raise
482 raise
479
483
480
484
481 class fileobjectproxy:
485 class fileobjectproxy:
482 """A proxy around file objects that tells a watcher when events occur.
486 """A proxy around file objects that tells a watcher when events occur.
483
487
484 This type is intended to only be used for testing purposes. Think hard
488 This type is intended to only be used for testing purposes. Think hard
485 before using it in important code.
489 before using it in important code.
486 """
490 """
487
491
488 __slots__ = (
492 __slots__ = (
489 '_orig',
493 '_orig',
490 '_observer',
494 '_observer',
491 )
495 )
492
496
493 def __init__(self, fh, observer):
497 def __init__(self, fh, observer):
494 object.__setattr__(self, '_orig', fh)
498 object.__setattr__(self, '_orig', fh)
495 object.__setattr__(self, '_observer', observer)
499 object.__setattr__(self, '_observer', observer)
496
500
497 def __getattribute__(self, name):
501 def __getattribute__(self, name):
498 ours = {
502 ours = {
499 '_observer',
503 '_observer',
500 # IOBase
504 # IOBase
501 'close',
505 'close',
502 # closed if a property
506 # closed if a property
503 'fileno',
507 'fileno',
504 'flush',
508 'flush',
505 'isatty',
509 'isatty',
506 'readable',
510 'readable',
507 'readline',
511 'readline',
508 'readlines',
512 'readlines',
509 'seek',
513 'seek',
510 'seekable',
514 'seekable',
511 'tell',
515 'tell',
512 'truncate',
516 'truncate',
513 'writable',
517 'writable',
514 'writelines',
518 'writelines',
515 # RawIOBase
519 # RawIOBase
516 'read',
520 'read',
517 'readall',
521 'readall',
518 'readinto',
522 'readinto',
519 'write',
523 'write',
520 # BufferedIOBase
524 # BufferedIOBase
521 # raw is a property
525 # raw is a property
522 'detach',
526 'detach',
523 # read defined above
527 # read defined above
524 'read1',
528 'read1',
525 # readinto defined above
529 # readinto defined above
526 # write defined above
530 # write defined above
527 }
531 }
528
532
529 # We only observe some methods.
533 # We only observe some methods.
530 if name in ours:
534 if name in ours:
531 return object.__getattribute__(self, name)
535 return object.__getattribute__(self, name)
532
536
533 return getattr(object.__getattribute__(self, '_orig'), name)
537 return getattr(object.__getattribute__(self, '_orig'), name)
534
538
535 def __nonzero__(self):
539 def __nonzero__(self):
536 return bool(object.__getattribute__(self, '_orig'))
540 return bool(object.__getattribute__(self, '_orig'))
537
541
538 __bool__ = __nonzero__
542 __bool__ = __nonzero__
539
543
540 def __delattr__(self, name):
544 def __delattr__(self, name):
541 return delattr(object.__getattribute__(self, '_orig'), name)
545 return delattr(object.__getattribute__(self, '_orig'), name)
542
546
543 def __setattr__(self, name, value):
547 def __setattr__(self, name, value):
544 return setattr(object.__getattribute__(self, '_orig'), name, value)
548 return setattr(object.__getattribute__(self, '_orig'), name, value)
545
549
546 def __iter__(self):
550 def __iter__(self):
547 return object.__getattribute__(self, '_orig').__iter__()
551 return object.__getattribute__(self, '_orig').__iter__()
548
552
549 def _observedcall(self, name, *args, **kwargs):
553 def _observedcall(self, name, *args, **kwargs):
550 # Call the original object.
554 # Call the original object.
551 orig = object.__getattribute__(self, '_orig')
555 orig = object.__getattribute__(self, '_orig')
552 res = getattr(orig, name)(*args, **kwargs)
556 res = getattr(orig, name)(*args, **kwargs)
553
557
554 # Call a method on the observer of the same name with arguments
558 # Call a method on the observer of the same name with arguments
555 # so it can react, log, etc.
559 # so it can react, log, etc.
556 observer = object.__getattribute__(self, '_observer')
560 observer = object.__getattribute__(self, '_observer')
557 fn = getattr(observer, name, None)
561 fn = getattr(observer, name, None)
558 if fn:
562 if fn:
559 fn(res, *args, **kwargs)
563 fn(res, *args, **kwargs)
560
564
561 return res
565 return res
562
566
563 def close(self, *args, **kwargs):
567 def close(self, *args, **kwargs):
564 return object.__getattribute__(self, '_observedcall')(
568 return object.__getattribute__(self, '_observedcall')(
565 'close', *args, **kwargs
569 'close', *args, **kwargs
566 )
570 )
567
571
568 def fileno(self, *args, **kwargs):
572 def fileno(self, *args, **kwargs):
569 return object.__getattribute__(self, '_observedcall')(
573 return object.__getattribute__(self, '_observedcall')(
570 'fileno', *args, **kwargs
574 'fileno', *args, **kwargs
571 )
575 )
572
576
573 def flush(self, *args, **kwargs):
577 def flush(self, *args, **kwargs):
574 return object.__getattribute__(self, '_observedcall')(
578 return object.__getattribute__(self, '_observedcall')(
575 'flush', *args, **kwargs
579 'flush', *args, **kwargs
576 )
580 )
577
581
578 def isatty(self, *args, **kwargs):
582 def isatty(self, *args, **kwargs):
579 return object.__getattribute__(self, '_observedcall')(
583 return object.__getattribute__(self, '_observedcall')(
580 'isatty', *args, **kwargs
584 'isatty', *args, **kwargs
581 )
585 )
582
586
583 def readable(self, *args, **kwargs):
587 def readable(self, *args, **kwargs):
584 return object.__getattribute__(self, '_observedcall')(
588 return object.__getattribute__(self, '_observedcall')(
585 'readable', *args, **kwargs
589 'readable', *args, **kwargs
586 )
590 )
587
591
588 def readline(self, *args, **kwargs):
592 def readline(self, *args, **kwargs):
589 return object.__getattribute__(self, '_observedcall')(
593 return object.__getattribute__(self, '_observedcall')(
590 'readline', *args, **kwargs
594 'readline', *args, **kwargs
591 )
595 )
592
596
593 def readlines(self, *args, **kwargs):
597 def readlines(self, *args, **kwargs):
594 return object.__getattribute__(self, '_observedcall')(
598 return object.__getattribute__(self, '_observedcall')(
595 'readlines', *args, **kwargs
599 'readlines', *args, **kwargs
596 )
600 )
597
601
598 def seek(self, *args, **kwargs):
602 def seek(self, *args, **kwargs):
599 return object.__getattribute__(self, '_observedcall')(
603 return object.__getattribute__(self, '_observedcall')(
600 'seek', *args, **kwargs
604 'seek', *args, **kwargs
601 )
605 )
602
606
603 def seekable(self, *args, **kwargs):
607 def seekable(self, *args, **kwargs):
604 return object.__getattribute__(self, '_observedcall')(
608 return object.__getattribute__(self, '_observedcall')(
605 'seekable', *args, **kwargs
609 'seekable', *args, **kwargs
606 )
610 )
607
611
608 def tell(self, *args, **kwargs):
612 def tell(self, *args, **kwargs):
609 return object.__getattribute__(self, '_observedcall')(
613 return object.__getattribute__(self, '_observedcall')(
610 'tell', *args, **kwargs
614 'tell', *args, **kwargs
611 )
615 )
612
616
613 def truncate(self, *args, **kwargs):
617 def truncate(self, *args, **kwargs):
614 return object.__getattribute__(self, '_observedcall')(
618 return object.__getattribute__(self, '_observedcall')(
615 'truncate', *args, **kwargs
619 'truncate', *args, **kwargs
616 )
620 )
617
621
618 def writable(self, *args, **kwargs):
622 def writable(self, *args, **kwargs):
619 return object.__getattribute__(self, '_observedcall')(
623 return object.__getattribute__(self, '_observedcall')(
620 'writable', *args, **kwargs
624 'writable', *args, **kwargs
621 )
625 )
622
626
623 def writelines(self, *args, **kwargs):
627 def writelines(self, *args, **kwargs):
624 return object.__getattribute__(self, '_observedcall')(
628 return object.__getattribute__(self, '_observedcall')(
625 'writelines', *args, **kwargs
629 'writelines', *args, **kwargs
626 )
630 )
627
631
628 def read(self, *args, **kwargs):
632 def read(self, *args, **kwargs):
629 return object.__getattribute__(self, '_observedcall')(
633 return object.__getattribute__(self, '_observedcall')(
630 'read', *args, **kwargs
634 'read', *args, **kwargs
631 )
635 )
632
636
633 def readall(self, *args, **kwargs):
637 def readall(self, *args, **kwargs):
634 return object.__getattribute__(self, '_observedcall')(
638 return object.__getattribute__(self, '_observedcall')(
635 'readall', *args, **kwargs
639 'readall', *args, **kwargs
636 )
640 )
637
641
638 def readinto(self, *args, **kwargs):
642 def readinto(self, *args, **kwargs):
639 return object.__getattribute__(self, '_observedcall')(
643 return object.__getattribute__(self, '_observedcall')(
640 'readinto', *args, **kwargs
644 'readinto', *args, **kwargs
641 )
645 )
642
646
643 def write(self, *args, **kwargs):
647 def write(self, *args, **kwargs):
644 return object.__getattribute__(self, '_observedcall')(
648 return object.__getattribute__(self, '_observedcall')(
645 'write', *args, **kwargs
649 'write', *args, **kwargs
646 )
650 )
647
651
648 def detach(self, *args, **kwargs):
652 def detach(self, *args, **kwargs):
649 return object.__getattribute__(self, '_observedcall')(
653 return object.__getattribute__(self, '_observedcall')(
650 'detach', *args, **kwargs
654 'detach', *args, **kwargs
651 )
655 )
652
656
653 def read1(self, *args, **kwargs):
657 def read1(self, *args, **kwargs):
654 return object.__getattribute__(self, '_observedcall')(
658 return object.__getattribute__(self, '_observedcall')(
655 'read1', *args, **kwargs
659 'read1', *args, **kwargs
656 )
660 )
657
661
658
662
659 class observedbufferedinputpipe(bufferedinputpipe):
663 class observedbufferedinputpipe(bufferedinputpipe):
660 """A variation of bufferedinputpipe that is aware of fileobjectproxy.
664 """A variation of bufferedinputpipe that is aware of fileobjectproxy.
661
665
662 ``bufferedinputpipe`` makes low-level calls to ``os.read()`` that
666 ``bufferedinputpipe`` makes low-level calls to ``os.read()`` that
663 bypass ``fileobjectproxy``. Because of this, we need to make
667 bypass ``fileobjectproxy``. Because of this, we need to make
664 ``bufferedinputpipe`` aware of these operations.
668 ``bufferedinputpipe`` aware of these operations.
665
669
666 This variation of ``bufferedinputpipe`` can notify observers about
670 This variation of ``bufferedinputpipe`` can notify observers about
667 ``os.read()`` events. It also re-publishes other events, such as
671 ``os.read()`` events. It also re-publishes other events, such as
668 ``read()`` and ``readline()``.
672 ``read()`` and ``readline()``.
669 """
673 """
670
674
671 def _fillbuffer(self, size=_chunksize):
675 def _fillbuffer(self, size=_chunksize):
672 res = super(observedbufferedinputpipe, self)._fillbuffer(size=size)
676 res = super(observedbufferedinputpipe, self)._fillbuffer(size=size)
673
677
674 fn = getattr(self._input._observer, 'osread', None)
678 fn = getattr(self._input._observer, 'osread', None)
675 if fn:
679 if fn:
676 fn(res, size)
680 fn(res, size)
677
681
678 return res
682 return res
679
683
680 # We use different observer methods because the operation isn't
684 # We use different observer methods because the operation isn't
681 # performed on the actual file object but on us.
685 # performed on the actual file object but on us.
682 def read(self, size):
686 def read(self, size):
683 res = super(observedbufferedinputpipe, self).read(size)
687 res = super(observedbufferedinputpipe, self).read(size)
684
688
685 fn = getattr(self._input._observer, 'bufferedread', None)
689 fn = getattr(self._input._observer, 'bufferedread', None)
686 if fn:
690 if fn:
687 fn(res, size)
691 fn(res, size)
688
692
689 return res
693 return res
690
694
691 def readline(self, *args, **kwargs):
695 def readline(self, *args, **kwargs):
692 res = super(observedbufferedinputpipe, self).readline(*args, **kwargs)
696 res = super(observedbufferedinputpipe, self).readline(*args, **kwargs)
693
697
694 fn = getattr(self._input._observer, 'bufferedreadline', None)
698 fn = getattr(self._input._observer, 'bufferedreadline', None)
695 if fn:
699 if fn:
696 fn(res)
700 fn(res)
697
701
698 return res
702 return res
699
703
700
704
701 PROXIED_SOCKET_METHODS = {
705 PROXIED_SOCKET_METHODS = {
702 'makefile',
706 'makefile',
703 'recv',
707 'recv',
704 'recvfrom',
708 'recvfrom',
705 'recvfrom_into',
709 'recvfrom_into',
706 'recv_into',
710 'recv_into',
707 'send',
711 'send',
708 'sendall',
712 'sendall',
709 'sendto',
713 'sendto',
710 'setblocking',
714 'setblocking',
711 'settimeout',
715 'settimeout',
712 'gettimeout',
716 'gettimeout',
713 'setsockopt',
717 'setsockopt',
714 }
718 }
715
719
716
720
717 class socketproxy:
721 class socketproxy:
718 """A proxy around a socket that tells a watcher when events occur.
722 """A proxy around a socket that tells a watcher when events occur.
719
723
720 This is like ``fileobjectproxy`` except for sockets.
724 This is like ``fileobjectproxy`` except for sockets.
721
725
722 This type is intended to only be used for testing purposes. Think hard
726 This type is intended to only be used for testing purposes. Think hard
723 before using it in important code.
727 before using it in important code.
724 """
728 """
725
729
726 __slots__ = (
730 __slots__ = (
727 '_orig',
731 '_orig',
728 '_observer',
732 '_observer',
729 )
733 )
730
734
731 def __init__(self, sock, observer):
735 def __init__(self, sock, observer):
732 object.__setattr__(self, '_orig', sock)
736 object.__setattr__(self, '_orig', sock)
733 object.__setattr__(self, '_observer', observer)
737 object.__setattr__(self, '_observer', observer)
734
738
735 def __getattribute__(self, name):
739 def __getattribute__(self, name):
736 if name in PROXIED_SOCKET_METHODS:
740 if name in PROXIED_SOCKET_METHODS:
737 return object.__getattribute__(self, name)
741 return object.__getattribute__(self, name)
738
742
739 return getattr(object.__getattribute__(self, '_orig'), name)
743 return getattr(object.__getattribute__(self, '_orig'), name)
740
744
741 def __delattr__(self, name):
745 def __delattr__(self, name):
742 return delattr(object.__getattribute__(self, '_orig'), name)
746 return delattr(object.__getattribute__(self, '_orig'), name)
743
747
744 def __setattr__(self, name, value):
748 def __setattr__(self, name, value):
745 return setattr(object.__getattribute__(self, '_orig'), name, value)
749 return setattr(object.__getattribute__(self, '_orig'), name, value)
746
750
747 def __nonzero__(self):
751 def __nonzero__(self):
748 return bool(object.__getattribute__(self, '_orig'))
752 return bool(object.__getattribute__(self, '_orig'))
749
753
750 __bool__ = __nonzero__
754 __bool__ = __nonzero__
751
755
752 def _observedcall(self, name, *args, **kwargs):
756 def _observedcall(self, name, *args, **kwargs):
753 # Call the original object.
757 # Call the original object.
754 orig = object.__getattribute__(self, '_orig')
758 orig = object.__getattribute__(self, '_orig')
755 res = getattr(orig, name)(*args, **kwargs)
759 res = getattr(orig, name)(*args, **kwargs)
756
760
757 # Call a method on the observer of the same name with arguments
761 # Call a method on the observer of the same name with arguments
758 # so it can react, log, etc.
762 # so it can react, log, etc.
759 observer = object.__getattribute__(self, '_observer')
763 observer = object.__getattribute__(self, '_observer')
760 fn = getattr(observer, name, None)
764 fn = getattr(observer, name, None)
761 if fn:
765 if fn:
762 fn(res, *args, **kwargs)
766 fn(res, *args, **kwargs)
763
767
764 return res
768 return res
765
769
766 def makefile(self, *args, **kwargs):
770 def makefile(self, *args, **kwargs):
767 res = object.__getattribute__(self, '_observedcall')(
771 res = object.__getattribute__(self, '_observedcall')(
768 'makefile', *args, **kwargs
772 'makefile', *args, **kwargs
769 )
773 )
770
774
771 # The file object may be used for I/O. So we turn it into a
775 # The file object may be used for I/O. So we turn it into a
772 # proxy using our observer.
776 # proxy using our observer.
773 observer = object.__getattribute__(self, '_observer')
777 observer = object.__getattribute__(self, '_observer')
774 return makeloggingfileobject(
778 return makeloggingfileobject(
775 observer.fh,
779 observer.fh,
776 res,
780 res,
777 observer.name,
781 observer.name,
778 reads=observer.reads,
782 reads=observer.reads,
779 writes=observer.writes,
783 writes=observer.writes,
780 logdata=observer.logdata,
784 logdata=observer.logdata,
781 logdataapis=observer.logdataapis,
785 logdataapis=observer.logdataapis,
782 )
786 )
783
787
784 def recv(self, *args, **kwargs):
788 def recv(self, *args, **kwargs):
785 return object.__getattribute__(self, '_observedcall')(
789 return object.__getattribute__(self, '_observedcall')(
786 'recv', *args, **kwargs
790 'recv', *args, **kwargs
787 )
791 )
788
792
789 def recvfrom(self, *args, **kwargs):
793 def recvfrom(self, *args, **kwargs):
790 return object.__getattribute__(self, '_observedcall')(
794 return object.__getattribute__(self, '_observedcall')(
791 'recvfrom', *args, **kwargs
795 'recvfrom', *args, **kwargs
792 )
796 )
793
797
794 def recvfrom_into(self, *args, **kwargs):
798 def recvfrom_into(self, *args, **kwargs):
795 return object.__getattribute__(self, '_observedcall')(
799 return object.__getattribute__(self, '_observedcall')(
796 'recvfrom_into', *args, **kwargs
800 'recvfrom_into', *args, **kwargs
797 )
801 )
798
802
799 def recv_into(self, *args, **kwargs):
803 def recv_into(self, *args, **kwargs):
800 return object.__getattribute__(self, '_observedcall')(
804 return object.__getattribute__(self, '_observedcall')(
801 'recv_info', *args, **kwargs
805 'recv_info', *args, **kwargs
802 )
806 )
803
807
804 def send(self, *args, **kwargs):
808 def send(self, *args, **kwargs):
805 return object.__getattribute__(self, '_observedcall')(
809 return object.__getattribute__(self, '_observedcall')(
806 'send', *args, **kwargs
810 'send', *args, **kwargs
807 )
811 )
808
812
809 def sendall(self, *args, **kwargs):
813 def sendall(self, *args, **kwargs):
810 return object.__getattribute__(self, '_observedcall')(
814 return object.__getattribute__(self, '_observedcall')(
811 'sendall', *args, **kwargs
815 'sendall', *args, **kwargs
812 )
816 )
813
817
814 def sendto(self, *args, **kwargs):
818 def sendto(self, *args, **kwargs):
815 return object.__getattribute__(self, '_observedcall')(
819 return object.__getattribute__(self, '_observedcall')(
816 'sendto', *args, **kwargs
820 'sendto', *args, **kwargs
817 )
821 )
818
822
819 def setblocking(self, *args, **kwargs):
823 def setblocking(self, *args, **kwargs):
820 return object.__getattribute__(self, '_observedcall')(
824 return object.__getattribute__(self, '_observedcall')(
821 'setblocking', *args, **kwargs
825 'setblocking', *args, **kwargs
822 )
826 )
823
827
824 def settimeout(self, *args, **kwargs):
828 def settimeout(self, *args, **kwargs):
825 return object.__getattribute__(self, '_observedcall')(
829 return object.__getattribute__(self, '_observedcall')(
826 'settimeout', *args, **kwargs
830 'settimeout', *args, **kwargs
827 )
831 )
828
832
829 def gettimeout(self, *args, **kwargs):
833 def gettimeout(self, *args, **kwargs):
830 return object.__getattribute__(self, '_observedcall')(
834 return object.__getattribute__(self, '_observedcall')(
831 'gettimeout', *args, **kwargs
835 'gettimeout', *args, **kwargs
832 )
836 )
833
837
834 def setsockopt(self, *args, **kwargs):
838 def setsockopt(self, *args, **kwargs):
835 return object.__getattribute__(self, '_observedcall')(
839 return object.__getattribute__(self, '_observedcall')(
836 'setsockopt', *args, **kwargs
840 'setsockopt', *args, **kwargs
837 )
841 )
838
842
839
843
840 class baseproxyobserver:
844 class baseproxyobserver:
841 def __init__(self, fh, name, logdata, logdataapis):
845 def __init__(self, fh, name, logdata, logdataapis):
842 self.fh = fh
846 self.fh = fh
843 self.name = name
847 self.name = name
844 self.logdata = logdata
848 self.logdata = logdata
845 self.logdataapis = logdataapis
849 self.logdataapis = logdataapis
846
850
847 def _writedata(self, data):
851 def _writedata(self, data):
848 if not self.logdata:
852 if not self.logdata:
849 if self.logdataapis:
853 if self.logdataapis:
850 self.fh.write(b'\n')
854 self.fh.write(b'\n')
851 self.fh.flush()
855 self.fh.flush()
852 return
856 return
853
857
854 # Simple case writes all data on a single line.
858 # Simple case writes all data on a single line.
855 if b'\n' not in data:
859 if b'\n' not in data:
856 if self.logdataapis:
860 if self.logdataapis:
857 self.fh.write(b': %s\n' % stringutil.escapestr(data))
861 self.fh.write(b': %s\n' % stringutil.escapestr(data))
858 else:
862 else:
859 self.fh.write(
863 self.fh.write(
860 b'%s> %s\n' % (self.name, stringutil.escapestr(data))
864 b'%s> %s\n' % (self.name, stringutil.escapestr(data))
861 )
865 )
862 self.fh.flush()
866 self.fh.flush()
863 return
867 return
864
868
865 # Data with newlines is written to multiple lines.
869 # Data with newlines is written to multiple lines.
866 if self.logdataapis:
870 if self.logdataapis:
867 self.fh.write(b':\n')
871 self.fh.write(b':\n')
868
872
869 lines = data.splitlines(True)
873 lines = data.splitlines(True)
870 for line in lines:
874 for line in lines:
871 self.fh.write(
875 self.fh.write(
872 b'%s> %s\n' % (self.name, stringutil.escapestr(line))
876 b'%s> %s\n' % (self.name, stringutil.escapestr(line))
873 )
877 )
874 self.fh.flush()
878 self.fh.flush()
875
879
876
880
877 class fileobjectobserver(baseproxyobserver):
881 class fileobjectobserver(baseproxyobserver):
878 """Logs file object activity."""
882 """Logs file object activity."""
879
883
880 def __init__(
884 def __init__(
881 self, fh, name, reads=True, writes=True, logdata=False, logdataapis=True
885 self, fh, name, reads=True, writes=True, logdata=False, logdataapis=True
882 ):
886 ):
883 super(fileobjectobserver, self).__init__(fh, name, logdata, logdataapis)
887 super(fileobjectobserver, self).__init__(fh, name, logdata, logdataapis)
884 self.reads = reads
888 self.reads = reads
885 self.writes = writes
889 self.writes = writes
886
890
887 def read(self, res, size=-1):
891 def read(self, res, size=-1):
888 if not self.reads:
892 if not self.reads:
889 return
893 return
890 # Python 3 can return None from reads at EOF instead of empty strings.
894 # Python 3 can return None from reads at EOF instead of empty strings.
891 if res is None:
895 if res is None:
892 res = b''
896 res = b''
893
897
894 if size == -1 and res == b'':
898 if size == -1 and res == b'':
895 # Suppress pointless read(-1) calls that return
899 # Suppress pointless read(-1) calls that return
896 # nothing. These happen _a lot_ on Python 3, and there
900 # nothing. These happen _a lot_ on Python 3, and there
897 # doesn't seem to be a better workaround to have matching
901 # doesn't seem to be a better workaround to have matching
898 # Python 2 and 3 behavior. :(
902 # Python 2 and 3 behavior. :(
899 return
903 return
900
904
901 if self.logdataapis:
905 if self.logdataapis:
902 self.fh.write(b'%s> read(%d) -> %d' % (self.name, size, len(res)))
906 self.fh.write(b'%s> read(%d) -> %d' % (self.name, size, len(res)))
903
907
904 self._writedata(res)
908 self._writedata(res)
905
909
906 def readline(self, res, limit=-1):
910 def readline(self, res, limit=-1):
907 if not self.reads:
911 if not self.reads:
908 return
912 return
909
913
910 if self.logdataapis:
914 if self.logdataapis:
911 self.fh.write(b'%s> readline() -> %d' % (self.name, len(res)))
915 self.fh.write(b'%s> readline() -> %d' % (self.name, len(res)))
912
916
913 self._writedata(res)
917 self._writedata(res)
914
918
915 def readinto(self, res, dest):
919 def readinto(self, res, dest):
916 if not self.reads:
920 if not self.reads:
917 return
921 return
918
922
919 if self.logdataapis:
923 if self.logdataapis:
920 self.fh.write(
924 self.fh.write(
921 b'%s> readinto(%d) -> %r' % (self.name, len(dest), res)
925 b'%s> readinto(%d) -> %r' % (self.name, len(dest), res)
922 )
926 )
923
927
924 data = dest[0:res] if res is not None else b''
928 data = dest[0:res] if res is not None else b''
925
929
926 # _writedata() uses "in" operator and is confused by memoryview because
930 # _writedata() uses "in" operator and is confused by memoryview because
927 # characters are ints on Python 3.
931 # characters are ints on Python 3.
928 if isinstance(data, memoryview):
932 if isinstance(data, memoryview):
929 data = data.tobytes()
933 data = data.tobytes()
930
934
931 self._writedata(data)
935 self._writedata(data)
932
936
933 def write(self, res, data):
937 def write(self, res, data):
934 if not self.writes:
938 if not self.writes:
935 return
939 return
936
940
937 # Python 2 returns None from some write() calls. Python 3 (reasonably)
941 # Python 2 returns None from some write() calls. Python 3 (reasonably)
938 # returns the integer bytes written.
942 # returns the integer bytes written.
939 if res is None and data:
943 if res is None and data:
940 res = len(data)
944 res = len(data)
941
945
942 if self.logdataapis:
946 if self.logdataapis:
943 self.fh.write(b'%s> write(%d) -> %r' % (self.name, len(data), res))
947 self.fh.write(b'%s> write(%d) -> %r' % (self.name, len(data), res))
944
948
945 self._writedata(data)
949 self._writedata(data)
946
950
947 def flush(self, res):
951 def flush(self, res):
948 if not self.writes:
952 if not self.writes:
949 return
953 return
950
954
951 self.fh.write(b'%s> flush() -> %r\n' % (self.name, res))
955 self.fh.write(b'%s> flush() -> %r\n' % (self.name, res))
952
956
953 # For observedbufferedinputpipe.
957 # For observedbufferedinputpipe.
954 def bufferedread(self, res, size):
958 def bufferedread(self, res, size):
955 if not self.reads:
959 if not self.reads:
956 return
960 return
957
961
958 if self.logdataapis:
962 if self.logdataapis:
959 self.fh.write(
963 self.fh.write(
960 b'%s> bufferedread(%d) -> %d' % (self.name, size, len(res))
964 b'%s> bufferedread(%d) -> %d' % (self.name, size, len(res))
961 )
965 )
962
966
963 self._writedata(res)
967 self._writedata(res)
964
968
965 def bufferedreadline(self, res):
969 def bufferedreadline(self, res):
966 if not self.reads:
970 if not self.reads:
967 return
971 return
968
972
969 if self.logdataapis:
973 if self.logdataapis:
970 self.fh.write(
974 self.fh.write(
971 b'%s> bufferedreadline() -> %d' % (self.name, len(res))
975 b'%s> bufferedreadline() -> %d' % (self.name, len(res))
972 )
976 )
973
977
974 self._writedata(res)
978 self._writedata(res)
975
979
976
980
977 def makeloggingfileobject(
981 def makeloggingfileobject(
978 logh, fh, name, reads=True, writes=True, logdata=False, logdataapis=True
982 logh, fh, name, reads=True, writes=True, logdata=False, logdataapis=True
979 ):
983 ):
980 """Turn a file object into a logging file object."""
984 """Turn a file object into a logging file object."""
981
985
982 observer = fileobjectobserver(
986 observer = fileobjectobserver(
983 logh,
987 logh,
984 name,
988 name,
985 reads=reads,
989 reads=reads,
986 writes=writes,
990 writes=writes,
987 logdata=logdata,
991 logdata=logdata,
988 logdataapis=logdataapis,
992 logdataapis=logdataapis,
989 )
993 )
990 return fileobjectproxy(fh, observer)
994 return fileobjectproxy(fh, observer)
991
995
992
996
993 class socketobserver(baseproxyobserver):
997 class socketobserver(baseproxyobserver):
994 """Logs socket activity."""
998 """Logs socket activity."""
995
999
996 def __init__(
1000 def __init__(
997 self,
1001 self,
998 fh,
1002 fh,
999 name,
1003 name,
1000 reads=True,
1004 reads=True,
1001 writes=True,
1005 writes=True,
1002 states=True,
1006 states=True,
1003 logdata=False,
1007 logdata=False,
1004 logdataapis=True,
1008 logdataapis=True,
1005 ):
1009 ):
1006 super(socketobserver, self).__init__(fh, name, logdata, logdataapis)
1010 super(socketobserver, self).__init__(fh, name, logdata, logdataapis)
1007 self.reads = reads
1011 self.reads = reads
1008 self.writes = writes
1012 self.writes = writes
1009 self.states = states
1013 self.states = states
1010
1014
1011 def makefile(self, res, mode=None, bufsize=None):
1015 def makefile(self, res, mode=None, bufsize=None):
1012 if not self.states:
1016 if not self.states:
1013 return
1017 return
1014
1018
1015 self.fh.write(b'%s> makefile(%r, %r)\n' % (self.name, mode, bufsize))
1019 self.fh.write(b'%s> makefile(%r, %r)\n' % (self.name, mode, bufsize))
1016
1020
1017 def recv(self, res, size, flags=0):
1021 def recv(self, res, size, flags=0):
1018 if not self.reads:
1022 if not self.reads:
1019 return
1023 return
1020
1024
1021 if self.logdataapis:
1025 if self.logdataapis:
1022 self.fh.write(
1026 self.fh.write(
1023 b'%s> recv(%d, %d) -> %d' % (self.name, size, flags, len(res))
1027 b'%s> recv(%d, %d) -> %d' % (self.name, size, flags, len(res))
1024 )
1028 )
1025 self._writedata(res)
1029 self._writedata(res)
1026
1030
1027 def recvfrom(self, res, size, flags=0):
1031 def recvfrom(self, res, size, flags=0):
1028 if not self.reads:
1032 if not self.reads:
1029 return
1033 return
1030
1034
1031 if self.logdataapis:
1035 if self.logdataapis:
1032 self.fh.write(
1036 self.fh.write(
1033 b'%s> recvfrom(%d, %d) -> %d'
1037 b'%s> recvfrom(%d, %d) -> %d'
1034 % (self.name, size, flags, len(res[0]))
1038 % (self.name, size, flags, len(res[0]))
1035 )
1039 )
1036
1040
1037 self._writedata(res[0])
1041 self._writedata(res[0])
1038
1042
1039 def recvfrom_into(self, res, buf, size, flags=0):
1043 def recvfrom_into(self, res, buf, size, flags=0):
1040 if not self.reads:
1044 if not self.reads:
1041 return
1045 return
1042
1046
1043 if self.logdataapis:
1047 if self.logdataapis:
1044 self.fh.write(
1048 self.fh.write(
1045 b'%s> recvfrom_into(%d, %d) -> %d'
1049 b'%s> recvfrom_into(%d, %d) -> %d'
1046 % (self.name, size, flags, res[0])
1050 % (self.name, size, flags, res[0])
1047 )
1051 )
1048
1052
1049 self._writedata(buf[0 : res[0]])
1053 self._writedata(buf[0 : res[0]])
1050
1054
1051 def recv_into(self, res, buf, size=0, flags=0):
1055 def recv_into(self, res, buf, size=0, flags=0):
1052 if not self.reads:
1056 if not self.reads:
1053 return
1057 return
1054
1058
1055 if self.logdataapis:
1059 if self.logdataapis:
1056 self.fh.write(
1060 self.fh.write(
1057 b'%s> recv_into(%d, %d) -> %d' % (self.name, size, flags, res)
1061 b'%s> recv_into(%d, %d) -> %d' % (self.name, size, flags, res)
1058 )
1062 )
1059
1063
1060 self._writedata(buf[0:res])
1064 self._writedata(buf[0:res])
1061
1065
1062 def send(self, res, data, flags=0):
1066 def send(self, res, data, flags=0):
1063 if not self.writes:
1067 if not self.writes:
1064 return
1068 return
1065
1069
1066 self.fh.write(
1070 self.fh.write(
1067 b'%s> send(%d, %d) -> %d' % (self.name, len(data), flags, len(res))
1071 b'%s> send(%d, %d) -> %d' % (self.name, len(data), flags, len(res))
1068 )
1072 )
1069 self._writedata(data)
1073 self._writedata(data)
1070
1074
1071 def sendall(self, res, data, flags=0):
1075 def sendall(self, res, data, flags=0):
1072 if not self.writes:
1076 if not self.writes:
1073 return
1077 return
1074
1078
1075 if self.logdataapis:
1079 if self.logdataapis:
1076 # Returns None on success. So don't bother reporting return value.
1080 # Returns None on success. So don't bother reporting return value.
1077 self.fh.write(
1081 self.fh.write(
1078 b'%s> sendall(%d, %d)' % (self.name, len(data), flags)
1082 b'%s> sendall(%d, %d)' % (self.name, len(data), flags)
1079 )
1083 )
1080
1084
1081 self._writedata(data)
1085 self._writedata(data)
1082
1086
1083 def sendto(self, res, data, flagsoraddress, address=None):
1087 def sendto(self, res, data, flagsoraddress, address=None):
1084 if not self.writes:
1088 if not self.writes:
1085 return
1089 return
1086
1090
1087 if address:
1091 if address:
1088 flags = flagsoraddress
1092 flags = flagsoraddress
1089 else:
1093 else:
1090 flags = 0
1094 flags = 0
1091
1095
1092 if self.logdataapis:
1096 if self.logdataapis:
1093 self.fh.write(
1097 self.fh.write(
1094 b'%s> sendto(%d, %d, %r) -> %d'
1098 b'%s> sendto(%d, %d, %r) -> %d'
1095 % (self.name, len(data), flags, address, res)
1099 % (self.name, len(data), flags, address, res)
1096 )
1100 )
1097
1101
1098 self._writedata(data)
1102 self._writedata(data)
1099
1103
1100 def setblocking(self, res, flag):
1104 def setblocking(self, res, flag):
1101 if not self.states:
1105 if not self.states:
1102 return
1106 return
1103
1107
1104 self.fh.write(b'%s> setblocking(%r)\n' % (self.name, flag))
1108 self.fh.write(b'%s> setblocking(%r)\n' % (self.name, flag))
1105
1109
1106 def settimeout(self, res, value):
1110 def settimeout(self, res, value):
1107 if not self.states:
1111 if not self.states:
1108 return
1112 return
1109
1113
1110 self.fh.write(b'%s> settimeout(%r)\n' % (self.name, value))
1114 self.fh.write(b'%s> settimeout(%r)\n' % (self.name, value))
1111
1115
1112 def gettimeout(self, res):
1116 def gettimeout(self, res):
1113 if not self.states:
1117 if not self.states:
1114 return
1118 return
1115
1119
1116 self.fh.write(b'%s> gettimeout() -> %f\n' % (self.name, res))
1120 self.fh.write(b'%s> gettimeout() -> %f\n' % (self.name, res))
1117
1121
1118 def setsockopt(self, res, level, optname, value):
1122 def setsockopt(self, res, level, optname, value):
1119 if not self.states:
1123 if not self.states:
1120 return
1124 return
1121
1125
1122 self.fh.write(
1126 self.fh.write(
1123 b'%s> setsockopt(%r, %r, %r) -> %r\n'
1127 b'%s> setsockopt(%r, %r, %r) -> %r\n'
1124 % (self.name, level, optname, value, res)
1128 % (self.name, level, optname, value, res)
1125 )
1129 )
1126
1130
1127
1131
1128 def makeloggingsocket(
1132 def makeloggingsocket(
1129 logh,
1133 logh,
1130 fh,
1134 fh,
1131 name,
1135 name,
1132 reads=True,
1136 reads=True,
1133 writes=True,
1137 writes=True,
1134 states=True,
1138 states=True,
1135 logdata=False,
1139 logdata=False,
1136 logdataapis=True,
1140 logdataapis=True,
1137 ):
1141 ):
1138 """Turn a socket into a logging socket."""
1142 """Turn a socket into a logging socket."""
1139
1143
1140 observer = socketobserver(
1144 observer = socketobserver(
1141 logh,
1145 logh,
1142 name,
1146 name,
1143 reads=reads,
1147 reads=reads,
1144 writes=writes,
1148 writes=writes,
1145 states=states,
1149 states=states,
1146 logdata=logdata,
1150 logdata=logdata,
1147 logdataapis=logdataapis,
1151 logdataapis=logdataapis,
1148 )
1152 )
1149 return socketproxy(fh, observer)
1153 return socketproxy(fh, observer)
1150
1154
1151
1155
1152 def version():
1156 def version():
1153 """Return version information if available."""
1157 """Return version information if available."""
1154 try:
1158 try:
1155 from . import __version__ # type: ignore
1159 from . import __version__ # type: ignore
1156
1160
1157 return __version__.version
1161 return __version__.version
1158 except ImportError:
1162 except ImportError:
1159 return b'unknown'
1163 return b'unknown'
1160
1164
1161
1165
1162 def versiontuple(v=None, n=4):
1166 def versiontuple(v=None, n=4):
1163 """Parses a Mercurial version string into an N-tuple.
1167 """Parses a Mercurial version string into an N-tuple.
1164
1168
1165 The version string to be parsed is specified with the ``v`` argument.
1169 The version string to be parsed is specified with the ``v`` argument.
1166 If it isn't defined, the current Mercurial version string will be parsed.
1170 If it isn't defined, the current Mercurial version string will be parsed.
1167
1171
1168 ``n`` can be 2, 3, or 4. Here is how some version strings map to
1172 ``n`` can be 2, 3, or 4. Here is how some version strings map to
1169 returned values:
1173 returned values:
1170
1174
1171 >>> v = b'3.6.1+190-df9b73d2d444'
1175 >>> v = b'3.6.1+190-df9b73d2d444'
1172 >>> versiontuple(v, 2)
1176 >>> versiontuple(v, 2)
1173 (3, 6)
1177 (3, 6)
1174 >>> versiontuple(v, 3)
1178 >>> versiontuple(v, 3)
1175 (3, 6, 1)
1179 (3, 6, 1)
1176 >>> versiontuple(v, 4)
1180 >>> versiontuple(v, 4)
1177 (3, 6, 1, '190-df9b73d2d444')
1181 (3, 6, 1, '190-df9b73d2d444')
1178
1182
1179 >>> versiontuple(b'3.6.1+190-df9b73d2d444+20151118')
1183 >>> versiontuple(b'3.6.1+190-df9b73d2d444+20151118')
1180 (3, 6, 1, '190-df9b73d2d444+20151118')
1184 (3, 6, 1, '190-df9b73d2d444+20151118')
1181
1185
1182 >>> v = b'3.6'
1186 >>> v = b'3.6'
1183 >>> versiontuple(v, 2)
1187 >>> versiontuple(v, 2)
1184 (3, 6)
1188 (3, 6)
1185 >>> versiontuple(v, 3)
1189 >>> versiontuple(v, 3)
1186 (3, 6, None)
1190 (3, 6, None)
1187 >>> versiontuple(v, 4)
1191 >>> versiontuple(v, 4)
1188 (3, 6, None, None)
1192 (3, 6, None, None)
1189
1193
1190 >>> v = b'3.9-rc'
1194 >>> v = b'3.9-rc'
1191 >>> versiontuple(v, 2)
1195 >>> versiontuple(v, 2)
1192 (3, 9)
1196 (3, 9)
1193 >>> versiontuple(v, 3)
1197 >>> versiontuple(v, 3)
1194 (3, 9, None)
1198 (3, 9, None)
1195 >>> versiontuple(v, 4)
1199 >>> versiontuple(v, 4)
1196 (3, 9, None, 'rc')
1200 (3, 9, None, 'rc')
1197
1201
1198 >>> v = b'3.9-rc+2-02a8fea4289b'
1202 >>> v = b'3.9-rc+2-02a8fea4289b'
1199 >>> versiontuple(v, 2)
1203 >>> versiontuple(v, 2)
1200 (3, 9)
1204 (3, 9)
1201 >>> versiontuple(v, 3)
1205 >>> versiontuple(v, 3)
1202 (3, 9, None)
1206 (3, 9, None)
1203 >>> versiontuple(v, 4)
1207 >>> versiontuple(v, 4)
1204 (3, 9, None, 'rc+2-02a8fea4289b')
1208 (3, 9, None, 'rc+2-02a8fea4289b')
1205
1209
1206 >>> versiontuple(b'4.6rc0')
1210 >>> versiontuple(b'4.6rc0')
1207 (4, 6, None, 'rc0')
1211 (4, 6, None, 'rc0')
1208 >>> versiontuple(b'4.6rc0+12-425d55e54f98')
1212 >>> versiontuple(b'4.6rc0+12-425d55e54f98')
1209 (4, 6, None, 'rc0+12-425d55e54f98')
1213 (4, 6, None, 'rc0+12-425d55e54f98')
1210 >>> versiontuple(b'.1.2.3')
1214 >>> versiontuple(b'.1.2.3')
1211 (None, None, None, '.1.2.3')
1215 (None, None, None, '.1.2.3')
1212 >>> versiontuple(b'12.34..5')
1216 >>> versiontuple(b'12.34..5')
1213 (12, 34, None, '..5')
1217 (12, 34, None, '..5')
1214 >>> versiontuple(b'1.2.3.4.5.6')
1218 >>> versiontuple(b'1.2.3.4.5.6')
1215 (1, 2, 3, '.4.5.6')
1219 (1, 2, 3, '.4.5.6')
1216 """
1220 """
1217 if not v:
1221 if not v:
1218 v = version()
1222 v = version()
1219 m = remod.match(br'(\d+(?:\.\d+){,2})[+-]?(.*)', v)
1223 m = remod.match(br'(\d+(?:\.\d+){,2})[+-]?(.*)', v)
1220 if not m:
1224 if not m:
1221 vparts, extra = b'', v
1225 vparts, extra = b'', v
1222 elif m.group(2):
1226 elif m.group(2):
1223 vparts, extra = m.groups()
1227 vparts, extra = m.groups()
1224 else:
1228 else:
1225 vparts, extra = m.group(1), None
1229 vparts, extra = m.group(1), None
1226
1230
1227 assert vparts is not None # help pytype
1231 assert vparts is not None # help pytype
1228
1232
1229 vints = []
1233 vints = []
1230 for i in vparts.split(b'.'):
1234 for i in vparts.split(b'.'):
1231 try:
1235 try:
1232 vints.append(int(i))
1236 vints.append(int(i))
1233 except ValueError:
1237 except ValueError:
1234 break
1238 break
1235 # (3, 6) -> (3, 6, None)
1239 # (3, 6) -> (3, 6, None)
1236 while len(vints) < 3:
1240 while len(vints) < 3:
1237 vints.append(None)
1241 vints.append(None)
1238
1242
1239 if n == 2:
1243 if n == 2:
1240 return (vints[0], vints[1])
1244 return (vints[0], vints[1])
1241 if n == 3:
1245 if n == 3:
1242 return (vints[0], vints[1], vints[2])
1246 return (vints[0], vints[1], vints[2])
1243 if n == 4:
1247 if n == 4:
1244 return (vints[0], vints[1], vints[2], extra)
1248 return (vints[0], vints[1], vints[2], extra)
1245
1249
1246 raise error.ProgrammingError(b"invalid version part request: %d" % n)
1250 raise error.ProgrammingError(b"invalid version part request: %d" % n)
1247
1251
1248
1252
1249 def cachefunc(func):
1253 def cachefunc(func):
1250 '''cache the result of function calls'''
1254 '''cache the result of function calls'''
1251 # XXX doesn't handle keywords args
1255 # XXX doesn't handle keywords args
1252 if func.__code__.co_argcount == 0:
1256 if func.__code__.co_argcount == 0:
1253 listcache = []
1257 listcache = []
1254
1258
1255 def f():
1259 def f():
1256 if len(listcache) == 0:
1260 if len(listcache) == 0:
1257 listcache.append(func())
1261 listcache.append(func())
1258 return listcache[0]
1262 return listcache[0]
1259
1263
1260 return f
1264 return f
1261 cache = {}
1265 cache = {}
1262 if func.__code__.co_argcount == 1:
1266 if func.__code__.co_argcount == 1:
1263 # we gain a small amount of time because
1267 # we gain a small amount of time because
1264 # we don't need to pack/unpack the list
1268 # we don't need to pack/unpack the list
1265 def f(arg):
1269 def f(arg):
1266 if arg not in cache:
1270 if arg not in cache:
1267 cache[arg] = func(arg)
1271 cache[arg] = func(arg)
1268 return cache[arg]
1272 return cache[arg]
1269
1273
1270 else:
1274 else:
1271
1275
1272 def f(*args):
1276 def f(*args):
1273 if args not in cache:
1277 if args not in cache:
1274 cache[args] = func(*args)
1278 cache[args] = func(*args)
1275 return cache[args]
1279 return cache[args]
1276
1280
1277 return f
1281 return f
1278
1282
1279
1283
1280 class cow:
1284 class cow:
1281 """helper class to make copy-on-write easier
1285 """helper class to make copy-on-write easier
1282
1286
1283 Call preparewrite before doing any writes.
1287 Call preparewrite before doing any writes.
1284 """
1288 """
1285
1289
1286 def preparewrite(self):
1290 def preparewrite(self):
1287 """call this before writes, return self or a copied new object"""
1291 """call this before writes, return self or a copied new object"""
1288 if getattr(self, '_copied', 0):
1292 if getattr(self, '_copied', 0):
1289 self._copied -= 1
1293 self._copied -= 1
1290 # Function cow.__init__ expects 1 arg(s), got 2 [wrong-arg-count]
1294 # Function cow.__init__ expects 1 arg(s), got 2 [wrong-arg-count]
1291 return self.__class__(self) # pytype: disable=wrong-arg-count
1295 return self.__class__(self) # pytype: disable=wrong-arg-count
1292 return self
1296 return self
1293
1297
1294 def copy(self):
1298 def copy(self):
1295 """always do a cheap copy"""
1299 """always do a cheap copy"""
1296 self._copied = getattr(self, '_copied', 0) + 1
1300 self._copied = getattr(self, '_copied', 0) + 1
1297 return self
1301 return self
1298
1302
1299
1303
1300 class sortdict(collections.OrderedDict):
1304 class sortdict(collections.OrderedDict):
1301 """a simple sorted dictionary
1305 """a simple sorted dictionary
1302
1306
1303 >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
1307 >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
1304 >>> d2 = d1.copy()
1308 >>> d2 = d1.copy()
1305 >>> list(d2.items())
1309 >>> list(d2.items())
1306 [('a', 0), ('b', 1)]
1310 [('a', 0), ('b', 1)]
1307 >>> d2.update([(b'a', 2)])
1311 >>> d2.update([(b'a', 2)])
1308 >>> list(d2.keys()) # should still be in last-set order
1312 >>> list(d2.keys()) # should still be in last-set order
1309 ['b', 'a']
1313 ['b', 'a']
1310 >>> d1.insert(1, b'a.5', 0.5)
1314 >>> d1.insert(1, b'a.5', 0.5)
1311 >>> list(d1.items())
1315 >>> list(d1.items())
1312 [('a', 0), ('a.5', 0.5), ('b', 1)]
1316 [('a', 0), ('a.5', 0.5), ('b', 1)]
1313 """
1317 """
1314
1318
1315 def __setitem__(self, key, value):
1319 def __setitem__(self, key, value):
1316 if key in self:
1320 if key in self:
1317 del self[key]
1321 del self[key]
1318 super(sortdict, self).__setitem__(key, value)
1322 super(sortdict, self).__setitem__(key, value)
1319
1323
1320 if pycompat.ispypy:
1324 if pycompat.ispypy:
1321 # __setitem__() isn't called as of PyPy 5.8.0
1325 # __setitem__() isn't called as of PyPy 5.8.0
1322 def update(self, src, **f):
1326 def update(self, src, **f):
1323 if isinstance(src, dict):
1327 if isinstance(src, dict):
1324 src = src.items()
1328 src = src.items()
1325 for k, v in src:
1329 for k, v in src:
1326 self[k] = v
1330 self[k] = v
1327 for k in f:
1331 for k in f:
1328 self[k] = f[k]
1332 self[k] = f[k]
1329
1333
1330 def insert(self, position, key, value):
1334 def insert(self, position, key, value):
1331 for i, (k, v) in enumerate(list(self.items())):
1335 for i, (k, v) in enumerate(list(self.items())):
1332 if i == position:
1336 if i == position:
1333 self[key] = value
1337 self[key] = value
1334 if i >= position:
1338 if i >= position:
1335 del self[k]
1339 del self[k]
1336 self[k] = v
1340 self[k] = v
1337
1341
1338
1342
1339 class cowdict(cow, dict):
1343 class cowdict(cow, dict):
1340 """copy-on-write dict
1344 """copy-on-write dict
1341
1345
1342 Be sure to call d = d.preparewrite() before writing to d.
1346 Be sure to call d = d.preparewrite() before writing to d.
1343
1347
1344 >>> a = cowdict()
1348 >>> a = cowdict()
1345 >>> a is a.preparewrite()
1349 >>> a is a.preparewrite()
1346 True
1350 True
1347 >>> b = a.copy()
1351 >>> b = a.copy()
1348 >>> b is a
1352 >>> b is a
1349 True
1353 True
1350 >>> c = b.copy()
1354 >>> c = b.copy()
1351 >>> c is a
1355 >>> c is a
1352 True
1356 True
1353 >>> a = a.preparewrite()
1357 >>> a = a.preparewrite()
1354 >>> b is a
1358 >>> b is a
1355 False
1359 False
1356 >>> a is a.preparewrite()
1360 >>> a is a.preparewrite()
1357 True
1361 True
1358 >>> c = c.preparewrite()
1362 >>> c = c.preparewrite()
1359 >>> b is c
1363 >>> b is c
1360 False
1364 False
1361 >>> b is b.preparewrite()
1365 >>> b is b.preparewrite()
1362 True
1366 True
1363 """
1367 """
1364
1368
1365
1369
1366 class cowsortdict(cow, sortdict):
1370 class cowsortdict(cow, sortdict):
1367 """copy-on-write sortdict
1371 """copy-on-write sortdict
1368
1372
1369 Be sure to call d = d.preparewrite() before writing to d.
1373 Be sure to call d = d.preparewrite() before writing to d.
1370 """
1374 """
1371
1375
1372
1376
1373 class transactional: # pytype: disable=ignored-metaclass
1377 class transactional: # pytype: disable=ignored-metaclass
1374 """Base class for making a transactional type into a context manager."""
1378 """Base class for making a transactional type into a context manager."""
1375
1379
1376 __metaclass__ = abc.ABCMeta
1380 __metaclass__ = abc.ABCMeta
1377
1381
1378 @abc.abstractmethod
1382 @abc.abstractmethod
1379 def close(self):
1383 def close(self):
1380 """Successfully closes the transaction."""
1384 """Successfully closes the transaction."""
1381
1385
1382 @abc.abstractmethod
1386 @abc.abstractmethod
1383 def release(self):
1387 def release(self):
1384 """Marks the end of the transaction.
1388 """Marks the end of the transaction.
1385
1389
1386 If the transaction has not been closed, it will be aborted.
1390 If the transaction has not been closed, it will be aborted.
1387 """
1391 """
1388
1392
1389 def __enter__(self):
1393 def __enter__(self):
1390 return self
1394 return self
1391
1395
1392 def __exit__(self, exc_type, exc_val, exc_tb):
1396 def __exit__(self, exc_type, exc_val, exc_tb):
1393 try:
1397 try:
1394 if exc_type is None:
1398 if exc_type is None:
1395 self.close()
1399 self.close()
1396 finally:
1400 finally:
1397 self.release()
1401 self.release()
1398
1402
1399
1403
1400 @contextlib.contextmanager
1404 @contextlib.contextmanager
1401 def acceptintervention(tr=None):
1405 def acceptintervention(tr=None):
1402 """A context manager that closes the transaction on InterventionRequired
1406 """A context manager that closes the transaction on InterventionRequired
1403
1407
1404 If no transaction was provided, this simply runs the body and returns
1408 If no transaction was provided, this simply runs the body and returns
1405 """
1409 """
1406 if not tr:
1410 if not tr:
1407 yield
1411 yield
1408 return
1412 return
1409 try:
1413 try:
1410 yield
1414 yield
1411 tr.close()
1415 tr.close()
1412 except error.InterventionRequired:
1416 except error.InterventionRequired:
1413 tr.close()
1417 tr.close()
1414 raise
1418 raise
1415 finally:
1419 finally:
1416 tr.release()
1420 tr.release()
1417
1421
1418
1422
1419 @contextlib.contextmanager
1423 @contextlib.contextmanager
1420 def nullcontextmanager(enter_result=None):
1424 def nullcontextmanager(enter_result=None):
1421 yield enter_result
1425 yield enter_result
1422
1426
1423
1427
1424 class _lrucachenode:
1428 class _lrucachenode:
1425 """A node in a doubly linked list.
1429 """A node in a doubly linked list.
1426
1430
1427 Holds a reference to nodes on either side as well as a key-value
1431 Holds a reference to nodes on either side as well as a key-value
1428 pair for the dictionary entry.
1432 pair for the dictionary entry.
1429 """
1433 """
1430
1434
1431 __slots__ = ('next', 'prev', 'key', 'value', 'cost')
1435 __slots__ = ('next', 'prev', 'key', 'value', 'cost')
1432
1436
1433 def __init__(self):
1437 def __init__(self):
1434 self.next = self
1438 self.next = self
1435 self.prev = self
1439 self.prev = self
1436
1440
1437 self.key = _notset
1441 self.key = _notset
1438 self.value = None
1442 self.value = None
1439 self.cost = 0
1443 self.cost = 0
1440
1444
1441 def markempty(self):
1445 def markempty(self):
1442 """Mark the node as emptied."""
1446 """Mark the node as emptied."""
1443 self.key = _notset
1447 self.key = _notset
1444 self.value = None
1448 self.value = None
1445 self.cost = 0
1449 self.cost = 0
1446
1450
1447
1451
1448 class lrucachedict:
1452 class lrucachedict:
1449 """Dict that caches most recent accesses and sets.
1453 """Dict that caches most recent accesses and sets.
1450
1454
1451 The dict consists of an actual backing dict - indexed by original
1455 The dict consists of an actual backing dict - indexed by original
1452 key - and a doubly linked circular list defining the order of entries in
1456 key - and a doubly linked circular list defining the order of entries in
1453 the cache.
1457 the cache.
1454
1458
1455 The head node is the newest entry in the cache. If the cache is full,
1459 The head node is the newest entry in the cache. If the cache is full,
1456 we recycle head.prev and make it the new head. Cache accesses result in
1460 we recycle head.prev and make it the new head. Cache accesses result in
1457 the node being moved to before the existing head and being marked as the
1461 the node being moved to before the existing head and being marked as the
1458 new head node.
1462 new head node.
1459
1463
1460 Items in the cache can be inserted with an optional "cost" value. This is
1464 Items in the cache can be inserted with an optional "cost" value. This is
1461 simply an integer that is specified by the caller. The cache can be queried
1465 simply an integer that is specified by the caller. The cache can be queried
1462 for the total cost of all items presently in the cache.
1466 for the total cost of all items presently in the cache.
1463
1467
1464 The cache can also define a maximum cost. If a cache insertion would
1468 The cache can also define a maximum cost. If a cache insertion would
1465 cause the total cost of the cache to go beyond the maximum cost limit,
1469 cause the total cost of the cache to go beyond the maximum cost limit,
1466 nodes will be evicted to make room for the new code. This can be used
1470 nodes will be evicted to make room for the new code. This can be used
1467 to e.g. set a max memory limit and associate an estimated bytes size
1471 to e.g. set a max memory limit and associate an estimated bytes size
1468 cost to each item in the cache. By default, no maximum cost is enforced.
1472 cost to each item in the cache. By default, no maximum cost is enforced.
1469 """
1473 """
1470
1474
1471 def __init__(self, max, maxcost=0):
1475 def __init__(self, max, maxcost=0):
1472 self._cache = {}
1476 self._cache = {}
1473
1477
1474 self._head = _lrucachenode()
1478 self._head = _lrucachenode()
1475 self._size = 1
1479 self._size = 1
1476 self.capacity = max
1480 self.capacity = max
1477 self.totalcost = 0
1481 self.totalcost = 0
1478 self.maxcost = maxcost
1482 self.maxcost = maxcost
1479
1483
1480 def __len__(self):
1484 def __len__(self):
1481 return len(self._cache)
1485 return len(self._cache)
1482
1486
1483 def __contains__(self, k):
1487 def __contains__(self, k):
1484 return k in self._cache
1488 return k in self._cache
1485
1489
1486 def __iter__(self):
1490 def __iter__(self):
1487 # We don't have to iterate in cache order, but why not.
1491 # We don't have to iterate in cache order, but why not.
1488 n = self._head
1492 n = self._head
1489 for i in range(len(self._cache)):
1493 for i in range(len(self._cache)):
1490 yield n.key
1494 yield n.key
1491 n = n.next
1495 n = n.next
1492
1496
1493 def __getitem__(self, k):
1497 def __getitem__(self, k):
1494 node = self._cache[k]
1498 node = self._cache[k]
1495 self._movetohead(node)
1499 self._movetohead(node)
1496 return node.value
1500 return node.value
1497
1501
1498 def insert(self, k, v, cost=0):
1502 def insert(self, k, v, cost=0):
1499 """Insert a new item in the cache with optional cost value."""
1503 """Insert a new item in the cache with optional cost value."""
1500 node = self._cache.get(k)
1504 node = self._cache.get(k)
1501 # Replace existing value and mark as newest.
1505 # Replace existing value and mark as newest.
1502 if node is not None:
1506 if node is not None:
1503 self.totalcost -= node.cost
1507 self.totalcost -= node.cost
1504 node.value = v
1508 node.value = v
1505 node.cost = cost
1509 node.cost = cost
1506 self.totalcost += cost
1510 self.totalcost += cost
1507 self._movetohead(node)
1511 self._movetohead(node)
1508
1512
1509 if self.maxcost:
1513 if self.maxcost:
1510 self._enforcecostlimit()
1514 self._enforcecostlimit()
1511
1515
1512 return
1516 return
1513
1517
1514 if self._size < self.capacity:
1518 if self._size < self.capacity:
1515 node = self._addcapacity()
1519 node = self._addcapacity()
1516 else:
1520 else:
1517 # Grab the last/oldest item.
1521 # Grab the last/oldest item.
1518 node = self._head.prev
1522 node = self._head.prev
1519
1523
1520 # At capacity. Kill the old entry.
1524 # At capacity. Kill the old entry.
1521 if node.key is not _notset:
1525 if node.key is not _notset:
1522 self.totalcost -= node.cost
1526 self.totalcost -= node.cost
1523 del self._cache[node.key]
1527 del self._cache[node.key]
1524
1528
1525 node.key = k
1529 node.key = k
1526 node.value = v
1530 node.value = v
1527 node.cost = cost
1531 node.cost = cost
1528 self.totalcost += cost
1532 self.totalcost += cost
1529 self._cache[k] = node
1533 self._cache[k] = node
1530 # And mark it as newest entry. No need to adjust order since it
1534 # And mark it as newest entry. No need to adjust order since it
1531 # is already self._head.prev.
1535 # is already self._head.prev.
1532 self._head = node
1536 self._head = node
1533
1537
1534 if self.maxcost:
1538 if self.maxcost:
1535 self._enforcecostlimit()
1539 self._enforcecostlimit()
1536
1540
1537 def __setitem__(self, k, v):
1541 def __setitem__(self, k, v):
1538 self.insert(k, v)
1542 self.insert(k, v)
1539
1543
1540 def __delitem__(self, k):
1544 def __delitem__(self, k):
1541 self.pop(k)
1545 self.pop(k)
1542
1546
1543 def pop(self, k, default=_notset):
1547 def pop(self, k, default=_notset):
1544 try:
1548 try:
1545 node = self._cache.pop(k)
1549 node = self._cache.pop(k)
1546 except KeyError:
1550 except KeyError:
1547 if default is _notset:
1551 if default is _notset:
1548 raise
1552 raise
1549 return default
1553 return default
1550
1554
1551 value = node.value
1555 value = node.value
1552 self.totalcost -= node.cost
1556 self.totalcost -= node.cost
1553 node.markempty()
1557 node.markempty()
1554
1558
1555 # Temporarily mark as newest item before re-adjusting head to make
1559 # Temporarily mark as newest item before re-adjusting head to make
1556 # this node the oldest item.
1560 # this node the oldest item.
1557 self._movetohead(node)
1561 self._movetohead(node)
1558 self._head = node.next
1562 self._head = node.next
1559
1563
1560 return value
1564 return value
1561
1565
1562 # Additional dict methods.
1566 # Additional dict methods.
1563
1567
1564 def get(self, k, default=None):
1568 def get(self, k, default=None):
1565 try:
1569 try:
1566 return self.__getitem__(k)
1570 return self.__getitem__(k)
1567 except KeyError:
1571 except KeyError:
1568 return default
1572 return default
1569
1573
1570 def peek(self, k, default=_notset):
1574 def peek(self, k, default=_notset):
1571 """Get the specified item without moving it to the head
1575 """Get the specified item without moving it to the head
1572
1576
1573 Unlike get(), this doesn't mutate the internal state. But be aware
1577 Unlike get(), this doesn't mutate the internal state. But be aware
1574 that it doesn't mean peek() is thread safe.
1578 that it doesn't mean peek() is thread safe.
1575 """
1579 """
1576 try:
1580 try:
1577 node = self._cache[k]
1581 node = self._cache[k]
1578 return node.value
1582 return node.value
1579 except KeyError:
1583 except KeyError:
1580 if default is _notset:
1584 if default is _notset:
1581 raise
1585 raise
1582 return default
1586 return default
1583
1587
1584 def clear(self):
1588 def clear(self):
1585 n = self._head
1589 n = self._head
1586 while n.key is not _notset:
1590 while n.key is not _notset:
1587 self.totalcost -= n.cost
1591 self.totalcost -= n.cost
1588 n.markempty()
1592 n.markempty()
1589 n = n.next
1593 n = n.next
1590
1594
1591 self._cache.clear()
1595 self._cache.clear()
1592
1596
1593 def copy(self, capacity=None, maxcost=0):
1597 def copy(self, capacity=None, maxcost=0):
1594 """Create a new cache as a copy of the current one.
1598 """Create a new cache as a copy of the current one.
1595
1599
1596 By default, the new cache has the same capacity as the existing one.
1600 By default, the new cache has the same capacity as the existing one.
1597 But, the cache capacity can be changed as part of performing the
1601 But, the cache capacity can be changed as part of performing the
1598 copy.
1602 copy.
1599
1603
1600 Items in the copy have an insertion/access order matching this
1604 Items in the copy have an insertion/access order matching this
1601 instance.
1605 instance.
1602 """
1606 """
1603
1607
1604 capacity = capacity or self.capacity
1608 capacity = capacity or self.capacity
1605 maxcost = maxcost or self.maxcost
1609 maxcost = maxcost or self.maxcost
1606 result = lrucachedict(capacity, maxcost=maxcost)
1610 result = lrucachedict(capacity, maxcost=maxcost)
1607
1611
1608 # We copy entries by iterating in oldest-to-newest order so the copy
1612 # We copy entries by iterating in oldest-to-newest order so the copy
1609 # has the correct ordering.
1613 # has the correct ordering.
1610
1614
1611 # Find the first non-empty entry.
1615 # Find the first non-empty entry.
1612 n = self._head.prev
1616 n = self._head.prev
1613 while n.key is _notset and n is not self._head:
1617 while n.key is _notset and n is not self._head:
1614 n = n.prev
1618 n = n.prev
1615
1619
1616 # We could potentially skip the first N items when decreasing capacity.
1620 # We could potentially skip the first N items when decreasing capacity.
1617 # But let's keep it simple unless it is a performance problem.
1621 # But let's keep it simple unless it is a performance problem.
1618 for i in range(len(self._cache)):
1622 for i in range(len(self._cache)):
1619 result.insert(n.key, n.value, cost=n.cost)
1623 result.insert(n.key, n.value, cost=n.cost)
1620 n = n.prev
1624 n = n.prev
1621
1625
1622 return result
1626 return result
1623
1627
1624 def popoldest(self):
1628 def popoldest(self):
1625 """Remove the oldest item from the cache.
1629 """Remove the oldest item from the cache.
1626
1630
1627 Returns the (key, value) describing the removed cache entry.
1631 Returns the (key, value) describing the removed cache entry.
1628 """
1632 """
1629 if not self._cache:
1633 if not self._cache:
1630 return
1634 return
1631
1635
1632 # Walk the linked list backwards starting at tail node until we hit
1636 # Walk the linked list backwards starting at tail node until we hit
1633 # a non-empty node.
1637 # a non-empty node.
1634 n = self._head.prev
1638 n = self._head.prev
1635
1639
1636 while n.key is _notset:
1640 while n.key is _notset:
1637 n = n.prev
1641 n = n.prev
1638
1642
1639 key, value = n.key, n.value
1643 key, value = n.key, n.value
1640
1644
1641 # And remove it from the cache and mark it as empty.
1645 # And remove it from the cache and mark it as empty.
1642 del self._cache[n.key]
1646 del self._cache[n.key]
1643 self.totalcost -= n.cost
1647 self.totalcost -= n.cost
1644 n.markempty()
1648 n.markempty()
1645
1649
1646 return key, value
1650 return key, value
1647
1651
1648 def _movetohead(self, node: _lrucachenode):
1652 def _movetohead(self, node: _lrucachenode):
1649 """Mark a node as the newest, making it the new head.
1653 """Mark a node as the newest, making it the new head.
1650
1654
1651 When a node is accessed, it becomes the freshest entry in the LRU
1655 When a node is accessed, it becomes the freshest entry in the LRU
1652 list, which is denoted by self._head.
1656 list, which is denoted by self._head.
1653
1657
1654 Visually, let's make ``N`` the new head node (* denotes head):
1658 Visually, let's make ``N`` the new head node (* denotes head):
1655
1659
1656 previous/oldest <-> head <-> next/next newest
1660 previous/oldest <-> head <-> next/next newest
1657
1661
1658 ----<->--- A* ---<->-----
1662 ----<->--- A* ---<->-----
1659 | |
1663 | |
1660 E <-> D <-> N <-> C <-> B
1664 E <-> D <-> N <-> C <-> B
1661
1665
1662 To:
1666 To:
1663
1667
1664 ----<->--- N* ---<->-----
1668 ----<->--- N* ---<->-----
1665 | |
1669 | |
1666 E <-> D <-> C <-> B <-> A
1670 E <-> D <-> C <-> B <-> A
1667
1671
1668 This requires the following moves:
1672 This requires the following moves:
1669
1673
1670 C.next = D (node.prev.next = node.next)
1674 C.next = D (node.prev.next = node.next)
1671 D.prev = C (node.next.prev = node.prev)
1675 D.prev = C (node.next.prev = node.prev)
1672 E.next = N (head.prev.next = node)
1676 E.next = N (head.prev.next = node)
1673 N.prev = E (node.prev = head.prev)
1677 N.prev = E (node.prev = head.prev)
1674 N.next = A (node.next = head)
1678 N.next = A (node.next = head)
1675 A.prev = N (head.prev = node)
1679 A.prev = N (head.prev = node)
1676 """
1680 """
1677 head = self._head
1681 head = self._head
1678 # C.next = D
1682 # C.next = D
1679 node.prev.next = node.next
1683 node.prev.next = node.next
1680 # D.prev = C
1684 # D.prev = C
1681 node.next.prev = node.prev
1685 node.next.prev = node.prev
1682 # N.prev = E
1686 # N.prev = E
1683 node.prev = head.prev
1687 node.prev = head.prev
1684 # N.next = A
1688 # N.next = A
1685 # It is tempting to do just "head" here, however if node is
1689 # It is tempting to do just "head" here, however if node is
1686 # adjacent to head, this will do bad things.
1690 # adjacent to head, this will do bad things.
1687 node.next = head.prev.next
1691 node.next = head.prev.next
1688 # E.next = N
1692 # E.next = N
1689 node.next.prev = node
1693 node.next.prev = node
1690 # A.prev = N
1694 # A.prev = N
1691 node.prev.next = node
1695 node.prev.next = node
1692
1696
1693 self._head = node
1697 self._head = node
1694
1698
1695 def _addcapacity(self) -> _lrucachenode:
1699 def _addcapacity(self) -> _lrucachenode:
1696 """Add a node to the circular linked list.
1700 """Add a node to the circular linked list.
1697
1701
1698 The new node is inserted before the head node.
1702 The new node is inserted before the head node.
1699 """
1703 """
1700 head = self._head
1704 head = self._head
1701 node = _lrucachenode()
1705 node = _lrucachenode()
1702 head.prev.next = node
1706 head.prev.next = node
1703 node.prev = head.prev
1707 node.prev = head.prev
1704 node.next = head
1708 node.next = head
1705 head.prev = node
1709 head.prev = node
1706 self._size += 1
1710 self._size += 1
1707 return node
1711 return node
1708
1712
1709 def _enforcecostlimit(self):
1713 def _enforcecostlimit(self):
1710 # This should run after an insertion. It should only be called if total
1714 # This should run after an insertion. It should only be called if total
1711 # cost limits are being enforced.
1715 # cost limits are being enforced.
1712 # The most recently inserted node is never evicted.
1716 # The most recently inserted node is never evicted.
1713 if len(self) <= 1 or self.totalcost <= self.maxcost:
1717 if len(self) <= 1 or self.totalcost <= self.maxcost:
1714 return
1718 return
1715
1719
1716 # This is logically equivalent to calling popoldest() until we
1720 # This is logically equivalent to calling popoldest() until we
1717 # free up enough cost. We don't do that since popoldest() needs
1721 # free up enough cost. We don't do that since popoldest() needs
1718 # to walk the linked list and doing this in a loop would be
1722 # to walk the linked list and doing this in a loop would be
1719 # quadratic. So we find the first non-empty node and then
1723 # quadratic. So we find the first non-empty node and then
1720 # walk nodes until we free up enough capacity.
1724 # walk nodes until we free up enough capacity.
1721 #
1725 #
1722 # If we only removed the minimum number of nodes to free enough
1726 # If we only removed the minimum number of nodes to free enough
1723 # cost at insert time, chances are high that the next insert would
1727 # cost at insert time, chances are high that the next insert would
1724 # also require pruning. This would effectively constitute quadratic
1728 # also require pruning. This would effectively constitute quadratic
1725 # behavior for insert-heavy workloads. To mitigate this, we set a
1729 # behavior for insert-heavy workloads. To mitigate this, we set a
1726 # target cost that is a percentage of the max cost. This will tend
1730 # target cost that is a percentage of the max cost. This will tend
1727 # to free more nodes when the high water mark is reached, which
1731 # to free more nodes when the high water mark is reached, which
1728 # lowers the chances of needing to prune on the subsequent insert.
1732 # lowers the chances of needing to prune on the subsequent insert.
1729 targetcost = int(self.maxcost * 0.75)
1733 targetcost = int(self.maxcost * 0.75)
1730
1734
1731 n = self._head.prev
1735 n = self._head.prev
1732 while n.key is _notset:
1736 while n.key is _notset:
1733 n = n.prev
1737 n = n.prev
1734
1738
1735 while len(self) > 1 and self.totalcost > targetcost:
1739 while len(self) > 1 and self.totalcost > targetcost:
1736 del self._cache[n.key]
1740 del self._cache[n.key]
1737 self.totalcost -= n.cost
1741 self.totalcost -= n.cost
1738 n.markempty()
1742 n.markempty()
1739 n = n.prev
1743 n = n.prev
1740
1744
1741
1745
1742 def lrucachefunc(func):
1746 def lrucachefunc(func):
1743 '''cache most recent results of function calls'''
1747 '''cache most recent results of function calls'''
1744 cache = {}
1748 cache = {}
1745 order = collections.deque()
1749 order = collections.deque()
1746 if func.__code__.co_argcount == 1:
1750 if func.__code__.co_argcount == 1:
1747
1751
1748 def f(arg):
1752 def f(arg):
1749 if arg not in cache:
1753 if arg not in cache:
1750 if len(cache) > 20:
1754 if len(cache) > 20:
1751 del cache[order.popleft()]
1755 del cache[order.popleft()]
1752 cache[arg] = func(arg)
1756 cache[arg] = func(arg)
1753 else:
1757 else:
1754 order.remove(arg)
1758 order.remove(arg)
1755 order.append(arg)
1759 order.append(arg)
1756 return cache[arg]
1760 return cache[arg]
1757
1761
1758 else:
1762 else:
1759
1763
1760 def f(*args):
1764 def f(*args):
1761 if args not in cache:
1765 if args not in cache:
1762 if len(cache) > 20:
1766 if len(cache) > 20:
1763 del cache[order.popleft()]
1767 del cache[order.popleft()]
1764 cache[args] = func(*args)
1768 cache[args] = func(*args)
1765 else:
1769 else:
1766 order.remove(args)
1770 order.remove(args)
1767 order.append(args)
1771 order.append(args)
1768 return cache[args]
1772 return cache[args]
1769
1773
1770 return f
1774 return f
1771
1775
1772
1776
1773 class propertycache:
1777 class propertycache:
1774 def __init__(self, func):
1778 def __init__(self, func):
1775 self.func = func
1779 self.func = func
1776 self.name = func.__name__
1780 self.name = func.__name__
1777
1781
1778 def __get__(self, obj, type=None):
1782 def __get__(self, obj, type=None):
1779 result = self.func(obj)
1783 result = self.func(obj)
1780 self.cachevalue(obj, result)
1784 self.cachevalue(obj, result)
1781 return result
1785 return result
1782
1786
1783 def cachevalue(self, obj, value):
1787 def cachevalue(self, obj, value):
1784 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
1788 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
1785 obj.__dict__[self.name] = value
1789 obj.__dict__[self.name] = value
1786
1790
1787
1791
1788 def clearcachedproperty(obj, prop):
1792 def clearcachedproperty(obj, prop):
1789 '''clear a cached property value, if one has been set'''
1793 '''clear a cached property value, if one has been set'''
1790 prop = pycompat.sysstr(prop)
1794 prop = pycompat.sysstr(prop)
1791 if prop in obj.__dict__:
1795 if prop in obj.__dict__:
1792 del obj.__dict__[prop]
1796 del obj.__dict__[prop]
1793
1797
1794
1798
1795 def increasingchunks(source, min=1024, max=65536):
1799 def increasingchunks(source, min=1024, max=65536):
1796 """return no less than min bytes per chunk while data remains,
1800 """return no less than min bytes per chunk while data remains,
1797 doubling min after each chunk until it reaches max"""
1801 doubling min after each chunk until it reaches max"""
1798
1802
1799 def log2(x):
1803 def log2(x):
1800 if not x:
1804 if not x:
1801 return 0
1805 return 0
1802 i = 0
1806 i = 0
1803 while x:
1807 while x:
1804 x >>= 1
1808 x >>= 1
1805 i += 1
1809 i += 1
1806 return i - 1
1810 return i - 1
1807
1811
1808 buf = []
1812 buf = []
1809 blen = 0
1813 blen = 0
1810 for chunk in source:
1814 for chunk in source:
1811 buf.append(chunk)
1815 buf.append(chunk)
1812 blen += len(chunk)
1816 blen += len(chunk)
1813 if blen >= min:
1817 if blen >= min:
1814 if min < max:
1818 if min < max:
1815 min = min << 1
1819 min = min << 1
1816 nmin = 1 << log2(blen)
1820 nmin = 1 << log2(blen)
1817 if nmin > min:
1821 if nmin > min:
1818 min = nmin
1822 min = nmin
1819 if min > max:
1823 if min > max:
1820 min = max
1824 min = max
1821 yield b''.join(buf)
1825 yield b''.join(buf)
1822 blen = 0
1826 blen = 0
1823 buf = []
1827 buf = []
1824 if buf:
1828 if buf:
1825 yield b''.join(buf)
1829 yield b''.join(buf)
1826
1830
1827
1831
1828 def always(fn):
1832 def always(fn):
1829 return True
1833 return True
1830
1834
1831
1835
1832 def never(fn):
1836 def never(fn):
1833 return False
1837 return False
1834
1838
1835
1839
1836 def nogc(func=None) -> Any:
1840 def nogc(func=None) -> Any:
1837 """disable garbage collector
1841 """disable garbage collector
1838
1842
1839 Python's garbage collector triggers a GC each time a certain number of
1843 Python's garbage collector triggers a GC each time a certain number of
1840 container objects (the number being defined by gc.get_threshold()) are
1844 container objects (the number being defined by gc.get_threshold()) are
1841 allocated even when marked not to be tracked by the collector. Tracking has
1845 allocated even when marked not to be tracked by the collector. Tracking has
1842 no effect on when GCs are triggered, only on what objects the GC looks
1846 no effect on when GCs are triggered, only on what objects the GC looks
1843 into. As a workaround, disable GC while building complex (huge)
1847 into. As a workaround, disable GC while building complex (huge)
1844 containers.
1848 containers.
1845
1849
1846 This garbage collector issue have been fixed in 2.7. But it still affect
1850 This garbage collector issue have been fixed in 2.7. But it still affect
1847 CPython's performance.
1851 CPython's performance.
1848 """
1852 """
1849 if func is None:
1853 if func is None:
1850 return _nogc_context()
1854 return _nogc_context()
1851 else:
1855 else:
1852 return _nogc_decorator(func)
1856 return _nogc_decorator(func)
1853
1857
1854
1858
1855 @contextlib.contextmanager
1859 @contextlib.contextmanager
1856 def _nogc_context():
1860 def _nogc_context():
1857 gcenabled = gc.isenabled()
1861 gcenabled = gc.isenabled()
1858 gc.disable()
1862 gc.disable()
1859 try:
1863 try:
1860 yield
1864 yield
1861 finally:
1865 finally:
1862 if gcenabled:
1866 if gcenabled:
1863 gc.enable()
1867 gc.enable()
1864
1868
1865
1869
1866 def _nogc_decorator(func):
1870 def _nogc_decorator(func):
1867 def wrapper(*args, **kwargs):
1871 def wrapper(*args, **kwargs):
1868 with _nogc_context():
1872 with _nogc_context():
1869 return func(*args, **kwargs)
1873 return func(*args, **kwargs)
1870
1874
1871 return wrapper
1875 return wrapper
1872
1876
1873
1877
1874 if pycompat.ispypy:
1878 if pycompat.ispypy:
1875 # PyPy runs slower with gc disabled
1879 # PyPy runs slower with gc disabled
1876 nogc = lambda x: x
1880 nogc = lambda x: x
1877
1881
1878
1882
1879 def pathto(root: bytes, n1: bytes, n2: bytes) -> bytes:
1883 def pathto(root: bytes, n1: bytes, n2: bytes) -> bytes:
1880 """return the relative path from one place to another.
1884 """return the relative path from one place to another.
1881 root should use os.sep to separate directories
1885 root should use os.sep to separate directories
1882 n1 should use os.sep to separate directories
1886 n1 should use os.sep to separate directories
1883 n2 should use "/" to separate directories
1887 n2 should use "/" to separate directories
1884 returns an os.sep-separated path.
1888 returns an os.sep-separated path.
1885
1889
1886 If n1 is a relative path, it's assumed it's
1890 If n1 is a relative path, it's assumed it's
1887 relative to root.
1891 relative to root.
1888 n2 should always be relative to root.
1892 n2 should always be relative to root.
1889 """
1893 """
1890 if not n1:
1894 if not n1:
1891 return localpath(n2)
1895 return localpath(n2)
1892 if os.path.isabs(n1):
1896 if os.path.isabs(n1):
1893 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
1897 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
1894 return os.path.join(root, localpath(n2))
1898 return os.path.join(root, localpath(n2))
1895 n2 = b'/'.join((pconvert(root), n2))
1899 n2 = b'/'.join((pconvert(root), n2))
1896 a, b = splitpath(n1), n2.split(b'/')
1900 a, b = splitpath(n1), n2.split(b'/')
1897 a.reverse()
1901 a.reverse()
1898 b.reverse()
1902 b.reverse()
1899 while a and b and a[-1] == b[-1]:
1903 while a and b and a[-1] == b[-1]:
1900 a.pop()
1904 a.pop()
1901 b.pop()
1905 b.pop()
1902 b.reverse()
1906 b.reverse()
1903 return pycompat.ossep.join(([b'..'] * len(a)) + b) or b'.'
1907 return pycompat.ossep.join(([b'..'] * len(a)) + b) or b'.'
1904
1908
1905
1909
1906 def checksignature(func, depth=1):
1910 def checksignature(func, depth=1):
1907 '''wrap a function with code to check for calling errors'''
1911 '''wrap a function with code to check for calling errors'''
1908
1912
1909 def check(*args, **kwargs):
1913 def check(*args, **kwargs):
1910 try:
1914 try:
1911 return func(*args, **kwargs)
1915 return func(*args, **kwargs)
1912 except TypeError:
1916 except TypeError:
1913 if len(traceback.extract_tb(sys.exc_info()[2])) == depth:
1917 if len(traceback.extract_tb(sys.exc_info()[2])) == depth:
1914 raise error.SignatureError
1918 raise error.SignatureError
1915 raise
1919 raise
1916
1920
1917 return check
1921 return check
1918
1922
1919
1923
1920 # a whilelist of known filesystems where hardlink works reliably
1924 # a whilelist of known filesystems where hardlink works reliably
1921 _hardlinkfswhitelist = {
1925 _hardlinkfswhitelist = {
1922 b'apfs',
1926 b'apfs',
1923 b'btrfs',
1927 b'btrfs',
1924 b'ext2',
1928 b'ext2',
1925 b'ext3',
1929 b'ext3',
1926 b'ext4',
1930 b'ext4',
1927 b'hfs',
1931 b'hfs',
1928 b'jfs',
1932 b'jfs',
1929 b'NTFS',
1933 b'NTFS',
1930 b'reiserfs',
1934 b'reiserfs',
1931 b'tmpfs',
1935 b'tmpfs',
1932 b'ufs',
1936 b'ufs',
1933 b'xfs',
1937 b'xfs',
1934 b'zfs',
1938 b'zfs',
1935 }
1939 }
1936
1940
1937
1941
1938 def copyfile(
1942 def copyfile(
1939 src,
1943 src,
1940 dest,
1944 dest,
1941 hardlink=False,
1945 hardlink=False,
1942 copystat=False,
1946 copystat=False,
1943 checkambig=False,
1947 checkambig=False,
1944 nb_bytes=None,
1948 nb_bytes=None,
1945 no_hardlink_cb=None,
1949 no_hardlink_cb=None,
1946 check_fs_hardlink=True,
1950 check_fs_hardlink=True,
1947 ):
1951 ):
1948 """copy a file, preserving mode and optionally other stat info like
1952 """copy a file, preserving mode and optionally other stat info like
1949 atime/mtime
1953 atime/mtime
1950
1954
1951 checkambig argument is used with filestat, and is useful only if
1955 checkambig argument is used with filestat, and is useful only if
1952 destination file is guarded by any lock (e.g. repo.lock or
1956 destination file is guarded by any lock (e.g. repo.lock or
1953 repo.wlock).
1957 repo.wlock).
1954
1958
1955 copystat and checkambig should be exclusive.
1959 copystat and checkambig should be exclusive.
1956
1960
1957 nb_bytes: if set only copy the first `nb_bytes` of the source file.
1961 nb_bytes: if set only copy the first `nb_bytes` of the source file.
1958 """
1962 """
1959 assert not (copystat and checkambig)
1963 assert not (copystat and checkambig)
1960 oldstat = None
1964 oldstat = None
1961 if os.path.lexists(dest):
1965 if os.path.lexists(dest):
1962 if checkambig:
1966 if checkambig:
1963 oldstat = checkambig and filestat.frompath(dest)
1967 oldstat = checkambig and filestat.frompath(dest)
1964 unlink(dest)
1968 unlink(dest)
1965 if hardlink and check_fs_hardlink:
1969 if hardlink and check_fs_hardlink:
1966 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1970 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1967 # unless we are confident that dest is on a whitelisted filesystem.
1971 # unless we are confident that dest is on a whitelisted filesystem.
1968 try:
1972 try:
1969 fstype = getfstype(os.path.dirname(dest))
1973 fstype = getfstype(os.path.dirname(dest))
1970 except OSError:
1974 except OSError:
1971 fstype = None
1975 fstype = None
1972 if fstype not in _hardlinkfswhitelist:
1976 if fstype not in _hardlinkfswhitelist:
1973 if no_hardlink_cb is not None:
1977 if no_hardlink_cb is not None:
1974 no_hardlink_cb()
1978 no_hardlink_cb()
1975 hardlink = False
1979 hardlink = False
1976 if hardlink:
1980 if hardlink:
1977 try:
1981 try:
1978 oslink(src, dest)
1982 oslink(src, dest)
1979 if nb_bytes is not None:
1983 if nb_bytes is not None:
1980 m = "the `nb_bytes` argument is incompatible with `hardlink`"
1984 m = "the `nb_bytes` argument is incompatible with `hardlink`"
1981 raise error.ProgrammingError(m)
1985 raise error.ProgrammingError(m)
1982 return
1986 return
1983 except (IOError, OSError) as exc:
1987 except (IOError, OSError) as exc:
1984 if exc.errno != errno.EEXIST and no_hardlink_cb is not None:
1988 if exc.errno != errno.EEXIST and no_hardlink_cb is not None:
1985 no_hardlink_cb()
1989 no_hardlink_cb()
1986 # fall back to normal copy
1990 # fall back to normal copy
1987 if os.path.islink(src):
1991 if os.path.islink(src):
1988 os.symlink(os.readlink(src), dest)
1992 os.symlink(os.readlink(src), dest)
1989 # copytime is ignored for symlinks, but in general copytime isn't needed
1993 # copytime is ignored for symlinks, but in general copytime isn't needed
1990 # for them anyway
1994 # for them anyway
1991 if nb_bytes is not None:
1995 if nb_bytes is not None:
1992 m = "cannot use `nb_bytes` on a symlink"
1996 m = "cannot use `nb_bytes` on a symlink"
1993 raise error.ProgrammingError(m)
1997 raise error.ProgrammingError(m)
1994 else:
1998 else:
1995 try:
1999 try:
1996 shutil.copyfile(src, dest)
2000 shutil.copyfile(src, dest)
1997 if copystat:
2001 if copystat:
1998 # copystat also copies mode
2002 # copystat also copies mode
1999 shutil.copystat(src, dest)
2003 shutil.copystat(src, dest)
2000 else:
2004 else:
2001 shutil.copymode(src, dest)
2005 shutil.copymode(src, dest)
2002 if oldstat and oldstat.stat:
2006 if oldstat and oldstat.stat:
2003 newstat = filestat.frompath(dest)
2007 newstat = filestat.frompath(dest)
2004 if newstat.isambig(oldstat):
2008 if newstat.isambig(oldstat):
2005 # stat of copied file is ambiguous to original one
2009 # stat of copied file is ambiguous to original one
2006 advanced = (
2010 advanced = (
2007 oldstat.stat[stat.ST_MTIME] + 1
2011 oldstat.stat[stat.ST_MTIME] + 1
2008 ) & 0x7FFFFFFF
2012 ) & 0x7FFFFFFF
2009 os.utime(dest, (advanced, advanced))
2013 os.utime(dest, (advanced, advanced))
2010 # We could do something smarter using `copy_file_range` call or similar
2014 # We could do something smarter using `copy_file_range` call or similar
2011 if nb_bytes is not None:
2015 if nb_bytes is not None:
2012 with open(dest, mode='r+') as f:
2016 with open(dest, mode='r+') as f:
2013 f.truncate(nb_bytes)
2017 f.truncate(nb_bytes)
2014 except shutil.Error as inst:
2018 except shutil.Error as inst:
2015 raise error.Abort(stringutil.forcebytestr(inst))
2019 raise error.Abort(stringutil.forcebytestr(inst))
2016
2020
2017
2021
2018 def copyfiles(src, dst, hardlink=None, progress=None):
2022 def copyfiles(src, dst, hardlink=None, progress=None):
2019 """Copy a directory tree using hardlinks if possible."""
2023 """Copy a directory tree using hardlinks if possible."""
2020 num = 0
2024 num = 0
2021
2025
2022 def settopic():
2026 def settopic():
2023 if progress:
2027 if progress:
2024 progress.topic = _(b'linking') if hardlink else _(b'copying')
2028 progress.topic = _(b'linking') if hardlink else _(b'copying')
2025
2029
2026 if os.path.isdir(src):
2030 if os.path.isdir(src):
2027 if hardlink is None:
2031 if hardlink is None:
2028 hardlink = (
2032 hardlink = (
2029 os.stat(src).st_dev == os.stat(os.path.dirname(dst)).st_dev
2033 os.stat(src).st_dev == os.stat(os.path.dirname(dst)).st_dev
2030 )
2034 )
2031 settopic()
2035 settopic()
2032 os.mkdir(dst)
2036 os.mkdir(dst)
2033 for name, kind in listdir(src):
2037 for name, kind in listdir(src):
2034 srcname = os.path.join(src, name)
2038 srcname = os.path.join(src, name)
2035 dstname = os.path.join(dst, name)
2039 dstname = os.path.join(dst, name)
2036 hardlink, n = copyfiles(srcname, dstname, hardlink, progress)
2040 hardlink, n = copyfiles(srcname, dstname, hardlink, progress)
2037 num += n
2041 num += n
2038 else:
2042 else:
2039 if hardlink is None:
2043 if hardlink is None:
2040 hardlink = (
2044 hardlink = (
2041 os.stat(os.path.dirname(src)).st_dev
2045 os.stat(os.path.dirname(src)).st_dev
2042 == os.stat(os.path.dirname(dst)).st_dev
2046 == os.stat(os.path.dirname(dst)).st_dev
2043 )
2047 )
2044 settopic()
2048 settopic()
2045
2049
2046 if hardlink:
2050 if hardlink:
2047 try:
2051 try:
2048 oslink(src, dst)
2052 oslink(src, dst)
2049 except (IOError, OSError) as exc:
2053 except (IOError, OSError) as exc:
2050 if exc.errno != errno.EEXIST:
2054 if exc.errno != errno.EEXIST:
2051 hardlink = False
2055 hardlink = False
2052 # XXX maybe try to relink if the file exist ?
2056 # XXX maybe try to relink if the file exist ?
2053 shutil.copy(src, dst)
2057 shutil.copy(src, dst)
2054 else:
2058 else:
2055 shutil.copy(src, dst)
2059 shutil.copy(src, dst)
2056 num += 1
2060 num += 1
2057 if progress:
2061 if progress:
2058 progress.increment()
2062 progress.increment()
2059
2063
2060 return hardlink, num
2064 return hardlink, num
2061
2065
2062
2066
2063 _winreservednames = {
2067 _winreservednames = {
2064 b'con',
2068 b'con',
2065 b'prn',
2069 b'prn',
2066 b'aux',
2070 b'aux',
2067 b'nul',
2071 b'nul',
2068 b'com1',
2072 b'com1',
2069 b'com2',
2073 b'com2',
2070 b'com3',
2074 b'com3',
2071 b'com4',
2075 b'com4',
2072 b'com5',
2076 b'com5',
2073 b'com6',
2077 b'com6',
2074 b'com7',
2078 b'com7',
2075 b'com8',
2079 b'com8',
2076 b'com9',
2080 b'com9',
2077 b'lpt1',
2081 b'lpt1',
2078 b'lpt2',
2082 b'lpt2',
2079 b'lpt3',
2083 b'lpt3',
2080 b'lpt4',
2084 b'lpt4',
2081 b'lpt5',
2085 b'lpt5',
2082 b'lpt6',
2086 b'lpt6',
2083 b'lpt7',
2087 b'lpt7',
2084 b'lpt8',
2088 b'lpt8',
2085 b'lpt9',
2089 b'lpt9',
2086 }
2090 }
2087 _winreservedchars = b':*?"<>|'
2091 _winreservedchars = b':*?"<>|'
2088
2092
2089
2093
2090 def checkwinfilename(path: bytes) -> Optional[bytes]:
2094 def checkwinfilename(path: bytes) -> Optional[bytes]:
2091 r"""Check that the base-relative path is a valid filename on Windows.
2095 r"""Check that the base-relative path is a valid filename on Windows.
2092 Returns None if the path is ok, or a UI string describing the problem.
2096 Returns None if the path is ok, or a UI string describing the problem.
2093
2097
2094 >>> checkwinfilename(b"just/a/normal/path")
2098 >>> checkwinfilename(b"just/a/normal/path")
2095 >>> checkwinfilename(b"foo/bar/con.xml")
2099 >>> checkwinfilename(b"foo/bar/con.xml")
2096 "filename contains 'con', which is reserved on Windows"
2100 "filename contains 'con', which is reserved on Windows"
2097 >>> checkwinfilename(b"foo/con.xml/bar")
2101 >>> checkwinfilename(b"foo/con.xml/bar")
2098 "filename contains 'con', which is reserved on Windows"
2102 "filename contains 'con', which is reserved on Windows"
2099 >>> checkwinfilename(b"foo/bar/xml.con")
2103 >>> checkwinfilename(b"foo/bar/xml.con")
2100 >>> checkwinfilename(b"foo/bar/AUX/bla.txt")
2104 >>> checkwinfilename(b"foo/bar/AUX/bla.txt")
2101 "filename contains 'AUX', which is reserved on Windows"
2105 "filename contains 'AUX', which is reserved on Windows"
2102 >>> checkwinfilename(b"foo/bar/bla:.txt")
2106 >>> checkwinfilename(b"foo/bar/bla:.txt")
2103 "filename contains ':', which is reserved on Windows"
2107 "filename contains ':', which is reserved on Windows"
2104 >>> checkwinfilename(b"foo/bar/b\07la.txt")
2108 >>> checkwinfilename(b"foo/bar/b\07la.txt")
2105 "filename contains '\\x07', which is invalid on Windows"
2109 "filename contains '\\x07', which is invalid on Windows"
2106 >>> checkwinfilename(b"foo/bar/bla ")
2110 >>> checkwinfilename(b"foo/bar/bla ")
2107 "filename ends with ' ', which is not allowed on Windows"
2111 "filename ends with ' ', which is not allowed on Windows"
2108 >>> checkwinfilename(b"../bar")
2112 >>> checkwinfilename(b"../bar")
2109 >>> checkwinfilename(b"foo\\")
2113 >>> checkwinfilename(b"foo\\")
2110 "filename ends with '\\', which is invalid on Windows"
2114 "filename ends with '\\', which is invalid on Windows"
2111 >>> checkwinfilename(b"foo\\/bar")
2115 >>> checkwinfilename(b"foo\\/bar")
2112 "directory name ends with '\\', which is invalid on Windows"
2116 "directory name ends with '\\', which is invalid on Windows"
2113 """
2117 """
2114 if path.endswith(b'\\'):
2118 if path.endswith(b'\\'):
2115 return _(b"filename ends with '\\', which is invalid on Windows")
2119 return _(b"filename ends with '\\', which is invalid on Windows")
2116 if b'\\/' in path:
2120 if b'\\/' in path:
2117 return _(b"directory name ends with '\\', which is invalid on Windows")
2121 return _(b"directory name ends with '\\', which is invalid on Windows")
2118 for n in path.replace(b'\\', b'/').split(b'/'):
2122 for n in path.replace(b'\\', b'/').split(b'/'):
2119 if not n:
2123 if not n:
2120 continue
2124 continue
2121 for c in _filenamebytestr(n):
2125 for c in _filenamebytestr(n):
2122 if c in _winreservedchars:
2126 if c in _winreservedchars:
2123 return (
2127 return (
2124 _(
2128 _(
2125 b"filename contains '%s', which is reserved "
2129 b"filename contains '%s', which is reserved "
2126 b"on Windows"
2130 b"on Windows"
2127 )
2131 )
2128 % c
2132 % c
2129 )
2133 )
2130 if ord(c) <= 31:
2134 if ord(c) <= 31:
2131 return _(
2135 return _(
2132 b"filename contains '%s', which is invalid on Windows"
2136 b"filename contains '%s', which is invalid on Windows"
2133 ) % stringutil.escapestr(c)
2137 ) % stringutil.escapestr(c)
2134 base = n.split(b'.')[0]
2138 base = n.split(b'.')[0]
2135 if base and base.lower() in _winreservednames:
2139 if base and base.lower() in _winreservednames:
2136 return (
2140 return (
2137 _(b"filename contains '%s', which is reserved on Windows")
2141 _(b"filename contains '%s', which is reserved on Windows")
2138 % base
2142 % base
2139 )
2143 )
2140 t = n[-1:]
2144 t = n[-1:]
2141 if t in b'. ' and n not in b'..':
2145 if t in b'. ' and n not in b'..':
2142 return (
2146 return (
2143 _(
2147 _(
2144 b"filename ends with '%s', which is not allowed "
2148 b"filename ends with '%s', which is not allowed "
2145 b"on Windows"
2149 b"on Windows"
2146 )
2150 )
2147 % t
2151 % t
2148 )
2152 )
2149
2153
2150
2154
2151 timer = getattr(time, "perf_counter", None)
2155 timer = getattr(time, "perf_counter", None)
2152
2156
2153 if pycompat.iswindows:
2157 if pycompat.iswindows:
2154 checkosfilename = checkwinfilename
2158 checkosfilename = checkwinfilename
2155 if not timer:
2159 if not timer:
2156 timer = time.clock # pytype: disable=module-attr
2160 timer = time.clock # pytype: disable=module-attr
2157 else:
2161 else:
2158 # mercurial.windows doesn't have platform.checkosfilename
2162 # mercurial.windows doesn't have platform.checkosfilename
2159 checkosfilename = platform.checkosfilename # pytype: disable=module-attr
2163 checkosfilename = platform.checkosfilename # pytype: disable=module-attr
2160 if not timer:
2164 if not timer:
2161 timer = time.time
2165 timer = time.time
2162
2166
2163
2167
2164 def makelock(info, pathname):
2168 def makelock(info, pathname):
2165 """Create a lock file atomically if possible
2169 """Create a lock file atomically if possible
2166
2170
2167 This may leave a stale lock file if symlink isn't supported and signal
2171 This may leave a stale lock file if symlink isn't supported and signal
2168 interrupt is enabled.
2172 interrupt is enabled.
2169 """
2173 """
2170 try:
2174 try:
2171 return os.symlink(info, pathname)
2175 return os.symlink(info, pathname)
2172 except OSError as why:
2176 except OSError as why:
2173 if why.errno == errno.EEXIST:
2177 if why.errno == errno.EEXIST:
2174 raise
2178 raise
2175 except AttributeError: # no symlink in os
2179 except AttributeError: # no symlink in os
2176 pass
2180 pass
2177
2181
2178 flags = os.O_CREAT | os.O_WRONLY | os.O_EXCL | getattr(os, 'O_BINARY', 0)
2182 flags = os.O_CREAT | os.O_WRONLY | os.O_EXCL | getattr(os, 'O_BINARY', 0)
2179 ld = os.open(pathname, flags)
2183 ld = os.open(pathname, flags)
2180 os.write(ld, info)
2184 os.write(ld, info)
2181 os.close(ld)
2185 os.close(ld)
2182
2186
2183
2187
2184 def readlock(pathname: bytes) -> bytes:
2188 def readlock(pathname: bytes) -> bytes:
2185 try:
2189 try:
2186 return readlink(pathname)
2190 return readlink(pathname)
2187 except OSError as why:
2191 except OSError as why:
2188 if why.errno not in (errno.EINVAL, errno.ENOSYS):
2192 if why.errno not in (errno.EINVAL, errno.ENOSYS):
2189 raise
2193 raise
2190 except AttributeError: # no symlink in os
2194 except AttributeError: # no symlink in os
2191 pass
2195 pass
2192 with posixfile(pathname, b'rb') as fp:
2196 with posixfile(pathname, b'rb') as fp:
2193 return fp.read()
2197 return fp.read()
2194
2198
2195
2199
2196 def fstat(fp):
2200 def fstat(fp):
2197 '''stat file object that may not have fileno method.'''
2201 '''stat file object that may not have fileno method.'''
2198 try:
2202 try:
2199 return os.fstat(fp.fileno())
2203 return os.fstat(fp.fileno())
2200 except AttributeError:
2204 except AttributeError:
2201 return os.stat(fp.name)
2205 return os.stat(fp.name)
2202
2206
2203
2207
2204 # File system features
2208 # File system features
2205
2209
2206
2210
2207 def fscasesensitive(path: bytes) -> bool:
2211 def fscasesensitive(path: bytes) -> bool:
2208 """
2212 """
2209 Return true if the given path is on a case-sensitive filesystem
2213 Return true if the given path is on a case-sensitive filesystem
2210
2214
2211 Requires a path (like /foo/.hg) ending with a foldable final
2215 Requires a path (like /foo/.hg) ending with a foldable final
2212 directory component.
2216 directory component.
2213 """
2217 """
2214 s1 = os.lstat(path)
2218 s1 = os.lstat(path)
2215 d, b = os.path.split(path)
2219 d, b = os.path.split(path)
2216 b2 = b.upper()
2220 b2 = b.upper()
2217 if b == b2:
2221 if b == b2:
2218 b2 = b.lower()
2222 b2 = b.lower()
2219 if b == b2:
2223 if b == b2:
2220 return True # no evidence against case sensitivity
2224 return True # no evidence against case sensitivity
2221 p2 = os.path.join(d, b2)
2225 p2 = os.path.join(d, b2)
2222 try:
2226 try:
2223 s2 = os.lstat(p2)
2227 s2 = os.lstat(p2)
2224 if s2 == s1:
2228 if s2 == s1:
2225 return False
2229 return False
2226 return True
2230 return True
2227 except OSError:
2231 except OSError:
2228 return True
2232 return True
2229
2233
2230
2234
2231 _re2_input = lambda x: x
2235 _re2_input = lambda x: x
2232 # google-re2 will need to be tell to not output error on its own
2236 # google-re2 will need to be tell to not output error on its own
2233 _re2_options = None
2237 _re2_options = None
2234 try:
2238 try:
2235 import re2 # pytype: disable=import-error
2239 import re2 # pytype: disable=import-error
2236
2240
2237 _re2 = None
2241 _re2 = None
2238 except ImportError:
2242 except ImportError:
2239 _re2 = False
2243 _re2 = False
2240
2244
2241
2245
2242 def has_re2():
2246 def has_re2():
2243 """return True is re2 is available, False otherwise"""
2247 """return True is re2 is available, False otherwise"""
2244 if _re2 is None:
2248 if _re2 is None:
2245 _re._checkre2()
2249 _re._checkre2()
2246 return _re2
2250 return _re2
2247
2251
2248
2252
2249 class _re:
2253 class _re:
2250 @staticmethod
2254 @staticmethod
2251 def _checkre2():
2255 def _checkre2():
2252 global _re2
2256 global _re2
2253 global _re2_input
2257 global _re2_input
2254 global _re2_options
2258 global _re2_options
2255 if _re2 is not None:
2259 if _re2 is not None:
2256 # we already have the answer
2260 # we already have the answer
2257 return
2261 return
2258
2262
2259 check_pattern = br'\[([^\[]+)\]'
2263 check_pattern = br'\[([^\[]+)\]'
2260 check_input = b'[ui]'
2264 check_input = b'[ui]'
2261 try:
2265 try:
2262 # check if match works, see issue3964
2266 # check if match works, see issue3964
2263 _re2 = bool(re2.match(check_pattern, check_input))
2267 _re2 = bool(re2.match(check_pattern, check_input))
2264 except ImportError:
2268 except ImportError:
2265 _re2 = False
2269 _re2 = False
2266 except TypeError:
2270 except TypeError:
2267 # the `pyre-2` project provides a re2 module that accept bytes
2271 # the `pyre-2` project provides a re2 module that accept bytes
2268 # the `fb-re2` project provides a re2 module that acccept sysstr
2272 # the `fb-re2` project provides a re2 module that acccept sysstr
2269 check_pattern = pycompat.sysstr(check_pattern)
2273 check_pattern = pycompat.sysstr(check_pattern)
2270 check_input = pycompat.sysstr(check_input)
2274 check_input = pycompat.sysstr(check_input)
2271 _re2 = bool(re2.match(check_pattern, check_input))
2275 _re2 = bool(re2.match(check_pattern, check_input))
2272 _re2_input = pycompat.sysstr
2276 _re2_input = pycompat.sysstr
2273 try:
2277 try:
2274 quiet = re2.Options()
2278 quiet = re2.Options()
2275 quiet.log_errors = False
2279 quiet.log_errors = False
2276 _re2_options = quiet
2280 _re2_options = quiet
2277 except AttributeError:
2281 except AttributeError:
2278 pass
2282 pass
2279
2283
2280 def compile(self, pat, flags=0):
2284 def compile(self, pat, flags=0):
2281 """Compile a regular expression, using re2 if possible
2285 """Compile a regular expression, using re2 if possible
2282
2286
2283 For best performance, use only re2-compatible regexp features. The
2287 For best performance, use only re2-compatible regexp features. The
2284 only flags from the re module that are re2-compatible are
2288 only flags from the re module that are re2-compatible are
2285 IGNORECASE and MULTILINE."""
2289 IGNORECASE and MULTILINE."""
2286 if _re2 is None:
2290 if _re2 is None:
2287 self._checkre2()
2291 self._checkre2()
2288 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
2292 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
2289 if flags & remod.IGNORECASE:
2293 if flags & remod.IGNORECASE:
2290 pat = b'(?i)' + pat
2294 pat = b'(?i)' + pat
2291 if flags & remod.MULTILINE:
2295 if flags & remod.MULTILINE:
2292 pat = b'(?m)' + pat
2296 pat = b'(?m)' + pat
2293 try:
2297 try:
2294 input_regex = _re2_input(pat)
2298 input_regex = _re2_input(pat)
2295 if _re2_options is not None:
2299 if _re2_options is not None:
2296 compiled = re2.compile(input_regex, options=_re2_options)
2300 compiled = re2.compile(input_regex, options=_re2_options)
2297 else:
2301 else:
2298 compiled = re2.compile(input_regex)
2302 compiled = re2.compile(input_regex)
2299 return compiled
2303 return compiled
2300 except re2.error:
2304 except re2.error:
2301 pass
2305 pass
2302 return remod.compile(pat, flags)
2306 return remod.compile(pat, flags)
2303
2307
2304 @propertycache
2308 @propertycache
2305 def escape(self):
2309 def escape(self):
2306 """Return the version of escape corresponding to self.compile.
2310 """Return the version of escape corresponding to self.compile.
2307
2311
2308 This is imperfect because whether re2 or re is used for a particular
2312 This is imperfect because whether re2 or re is used for a particular
2309 function depends on the flags, etc, but it's the best we can do.
2313 function depends on the flags, etc, but it's the best we can do.
2310 """
2314 """
2311 global _re2
2315 global _re2
2312 if _re2 is None:
2316 if _re2 is None:
2313 self._checkre2()
2317 self._checkre2()
2314 if _re2:
2318 if _re2:
2315 return re2.escape
2319 return re2.escape
2316 else:
2320 else:
2317 return remod.escape
2321 return remod.escape
2318
2322
2319
2323
2320 re = _re()
2324 re = _re()
2321
2325
2322 _fspathcache = {}
2326 _fspathcache = {}
2323
2327
2324
2328
2325 def fspath(name: bytes, root: bytes) -> bytes:
2329 def fspath(name: bytes, root: bytes) -> bytes:
2326 """Get name in the case stored in the filesystem
2330 """Get name in the case stored in the filesystem
2327
2331
2328 The name should be relative to root, and be normcase-ed for efficiency.
2332 The name should be relative to root, and be normcase-ed for efficiency.
2329
2333
2330 Note that this function is unnecessary, and should not be
2334 Note that this function is unnecessary, and should not be
2331 called, for case-sensitive filesystems (simply because it's expensive).
2335 called, for case-sensitive filesystems (simply because it's expensive).
2332
2336
2333 The root should be normcase-ed, too.
2337 The root should be normcase-ed, too.
2334 """
2338 """
2335
2339
2336 def _makefspathcacheentry(dir):
2340 def _makefspathcacheentry(dir):
2337 return {normcase(n): n for n in os.listdir(dir)}
2341 return {normcase(n): n for n in os.listdir(dir)}
2338
2342
2339 seps = pycompat.ossep
2343 seps = pycompat.ossep
2340 if pycompat.osaltsep:
2344 if pycompat.osaltsep:
2341 seps = seps + pycompat.osaltsep
2345 seps = seps + pycompat.osaltsep
2342 # Protect backslashes. This gets silly very quickly.
2346 # Protect backslashes. This gets silly very quickly.
2343 seps.replace(b'\\', b'\\\\')
2347 seps.replace(b'\\', b'\\\\')
2344 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
2348 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
2345 dir = os.path.normpath(root)
2349 dir = os.path.normpath(root)
2346 result = []
2350 result = []
2347 for part, sep in pattern.findall(name):
2351 for part, sep in pattern.findall(name):
2348 if sep:
2352 if sep:
2349 result.append(sep)
2353 result.append(sep)
2350 continue
2354 continue
2351
2355
2352 if dir not in _fspathcache:
2356 if dir not in _fspathcache:
2353 _fspathcache[dir] = _makefspathcacheentry(dir)
2357 _fspathcache[dir] = _makefspathcacheentry(dir)
2354 contents = _fspathcache[dir]
2358 contents = _fspathcache[dir]
2355
2359
2356 found = contents.get(part)
2360 found = contents.get(part)
2357 if not found:
2361 if not found:
2358 # retry "once per directory" per "dirstate.walk" which
2362 # retry "once per directory" per "dirstate.walk" which
2359 # may take place for each patches of "hg qpush", for example
2363 # may take place for each patches of "hg qpush", for example
2360 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
2364 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
2361 found = contents.get(part)
2365 found = contents.get(part)
2362
2366
2363 result.append(found or part)
2367 result.append(found or part)
2364 dir = os.path.join(dir, part)
2368 dir = os.path.join(dir, part)
2365
2369
2366 return b''.join(result)
2370 return b''.join(result)
2367
2371
2368
2372
2369 def checknlink(testfile: bytes) -> bool:
2373 def checknlink(testfile: bytes) -> bool:
2370 '''check whether hardlink count reporting works properly'''
2374 '''check whether hardlink count reporting works properly'''
2371
2375
2372 # testfile may be open, so we need a separate file for checking to
2376 # testfile may be open, so we need a separate file for checking to
2373 # work around issue2543 (or testfile may get lost on Samba shares)
2377 # work around issue2543 (or testfile may get lost on Samba shares)
2374 f1, f2, fp = None, None, None
2378 f1, f2, fp = None, None, None
2375 try:
2379 try:
2376 fd, f1 = pycompat.mkstemp(
2380 fd, f1 = pycompat.mkstemp(
2377 prefix=b'.%s-' % os.path.basename(testfile),
2381 prefix=b'.%s-' % os.path.basename(testfile),
2378 suffix=b'1~',
2382 suffix=b'1~',
2379 dir=os.path.dirname(testfile),
2383 dir=os.path.dirname(testfile),
2380 )
2384 )
2381 os.close(fd)
2385 os.close(fd)
2382 f2 = b'%s2~' % f1[:-2]
2386 f2 = b'%s2~' % f1[:-2]
2383
2387
2384 oslink(f1, f2)
2388 oslink(f1, f2)
2385 # nlinks() may behave differently for files on Windows shares if
2389 # nlinks() may behave differently for files on Windows shares if
2386 # the file is open.
2390 # the file is open.
2387 fp = posixfile(f2)
2391 fp = posixfile(f2)
2388 return nlinks(f2) > 1
2392 return nlinks(f2) > 1
2389 except OSError:
2393 except OSError:
2390 return False
2394 return False
2391 finally:
2395 finally:
2392 if fp is not None:
2396 if fp is not None:
2393 fp.close()
2397 fp.close()
2394 for f in (f1, f2):
2398 for f in (f1, f2):
2395 try:
2399 try:
2396 if f is not None:
2400 if f is not None:
2397 os.unlink(f)
2401 os.unlink(f)
2398 except OSError:
2402 except OSError:
2399 pass
2403 pass
2400
2404
2401
2405
2402 def endswithsep(path: bytes) -> bool:
2406 def endswithsep(path: bytes) -> bool:
2403 '''Check path ends with os.sep or os.altsep.'''
2407 '''Check path ends with os.sep or os.altsep.'''
2404 return bool( # help pytype
2408 return bool( # help pytype
2405 path.endswith(pycompat.ossep)
2409 path.endswith(pycompat.ossep)
2406 or pycompat.osaltsep
2410 or pycompat.osaltsep
2407 and path.endswith(pycompat.osaltsep)
2411 and path.endswith(pycompat.osaltsep)
2408 )
2412 )
2409
2413
2410
2414
2411 def splitpath(path: bytes) -> List[bytes]:
2415 def splitpath(path: bytes) -> List[bytes]:
2412 """Split path by os.sep.
2416 """Split path by os.sep.
2413 Note that this function does not use os.altsep because this is
2417 Note that this function does not use os.altsep because this is
2414 an alternative of simple "xxx.split(os.sep)".
2418 an alternative of simple "xxx.split(os.sep)".
2415 It is recommended to use os.path.normpath() before using this
2419 It is recommended to use os.path.normpath() before using this
2416 function if need."""
2420 function if need."""
2417 return path.split(pycompat.ossep)
2421 return path.split(pycompat.ossep)
2418
2422
2419
2423
2420 def mktempcopy(name, emptyok=False, createmode=None, enforcewritable=False):
2424 def mktempcopy(name, emptyok=False, createmode=None, enforcewritable=False):
2421 """Create a temporary file with the same contents from name
2425 """Create a temporary file with the same contents from name
2422
2426
2423 The permission bits are copied from the original file.
2427 The permission bits are copied from the original file.
2424
2428
2425 If the temporary file is going to be truncated immediately, you
2429 If the temporary file is going to be truncated immediately, you
2426 can use emptyok=True as an optimization.
2430 can use emptyok=True as an optimization.
2427
2431
2428 Returns the name of the temporary file.
2432 Returns the name of the temporary file.
2429 """
2433 """
2430 d, fn = os.path.split(name)
2434 d, fn = os.path.split(name)
2431 fd, temp = pycompat.mkstemp(prefix=b'.%s-' % fn, suffix=b'~', dir=d)
2435 fd, temp = pycompat.mkstemp(prefix=b'.%s-' % fn, suffix=b'~', dir=d)
2432 os.close(fd)
2436 os.close(fd)
2433 # Temporary files are created with mode 0600, which is usually not
2437 # Temporary files are created with mode 0600, which is usually not
2434 # what we want. If the original file already exists, just copy
2438 # what we want. If the original file already exists, just copy
2435 # its mode. Otherwise, manually obey umask.
2439 # its mode. Otherwise, manually obey umask.
2436 copymode(name, temp, createmode, enforcewritable)
2440 copymode(name, temp, createmode, enforcewritable)
2437
2441
2438 if emptyok:
2442 if emptyok:
2439 return temp
2443 return temp
2440 try:
2444 try:
2441 try:
2445 try:
2442 ifp = posixfile(name, b"rb")
2446 ifp = posixfile(name, b"rb")
2443 except IOError as inst:
2447 except IOError as inst:
2444 if inst.errno == errno.ENOENT:
2448 if inst.errno == errno.ENOENT:
2445 return temp
2449 return temp
2446 if not getattr(inst, 'filename', None):
2450 if not getattr(inst, 'filename', None):
2447 inst.filename = name
2451 inst.filename = name
2448 raise
2452 raise
2449 ofp = posixfile(temp, b"wb")
2453 ofp = posixfile(temp, b"wb")
2450 for chunk in filechunkiter(ifp):
2454 for chunk in filechunkiter(ifp):
2451 ofp.write(chunk)
2455 ofp.write(chunk)
2452 ifp.close()
2456 ifp.close()
2453 ofp.close()
2457 ofp.close()
2454 except: # re-raises
2458 except: # re-raises
2455 try:
2459 try:
2456 os.unlink(temp)
2460 os.unlink(temp)
2457 except OSError:
2461 except OSError:
2458 pass
2462 pass
2459 raise
2463 raise
2460 return temp
2464 return temp
2461
2465
2462
2466
2463 class filestat:
2467 class filestat:
2464 """help to exactly detect change of a file
2468 """help to exactly detect change of a file
2465
2469
2466 'stat' attribute is result of 'os.stat()' if specified 'path'
2470 'stat' attribute is result of 'os.stat()' if specified 'path'
2467 exists. Otherwise, it is None. This can avoid preparative
2471 exists. Otherwise, it is None. This can avoid preparative
2468 'exists()' examination on client side of this class.
2472 'exists()' examination on client side of this class.
2469 """
2473 """
2470
2474
2471 def __init__(self, stat):
2475 def __init__(self, stat):
2472 self.stat = stat
2476 self.stat = stat
2473
2477
2474 @classmethod
2478 @classmethod
2475 def frompath(cls, path):
2479 def frompath(cls, path):
2476 try:
2480 try:
2477 stat = os.stat(path)
2481 stat = os.stat(path)
2478 except FileNotFoundError:
2482 except FileNotFoundError:
2479 stat = None
2483 stat = None
2480 return cls(stat)
2484 return cls(stat)
2481
2485
2482 @classmethod
2486 @classmethod
2483 def fromfp(cls, fp):
2487 def fromfp(cls, fp):
2484 stat = os.fstat(fp.fileno())
2488 stat = os.fstat(fp.fileno())
2485 return cls(stat)
2489 return cls(stat)
2486
2490
2487 __hash__ = object.__hash__
2491 __hash__ = object.__hash__
2488
2492
2489 def __eq__(self, old):
2493 def __eq__(self, old):
2490 try:
2494 try:
2491 # if ambiguity between stat of new and old file is
2495 # if ambiguity between stat of new and old file is
2492 # avoided, comparison of size, ctime and mtime is enough
2496 # avoided, comparison of size, ctime and mtime is enough
2493 # to exactly detect change of a file regardless of platform
2497 # to exactly detect change of a file regardless of platform
2494 return (
2498 return (
2495 self.stat.st_size == old.stat.st_size
2499 self.stat.st_size == old.stat.st_size
2496 and self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME]
2500 and self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME]
2497 and self.stat[stat.ST_MTIME] == old.stat[stat.ST_MTIME]
2501 and self.stat[stat.ST_MTIME] == old.stat[stat.ST_MTIME]
2498 )
2502 )
2499 except AttributeError:
2503 except AttributeError:
2500 pass
2504 pass
2501 try:
2505 try:
2502 return self.stat is None and old.stat is None
2506 return self.stat is None and old.stat is None
2503 except AttributeError:
2507 except AttributeError:
2504 return False
2508 return False
2505
2509
2506 def isambig(self, old):
2510 def isambig(self, old):
2507 """Examine whether new (= self) stat is ambiguous against old one
2511 """Examine whether new (= self) stat is ambiguous against old one
2508
2512
2509 "S[N]" below means stat of a file at N-th change:
2513 "S[N]" below means stat of a file at N-th change:
2510
2514
2511 - S[n-1].ctime < S[n].ctime: can detect change of a file
2515 - S[n-1].ctime < S[n].ctime: can detect change of a file
2512 - S[n-1].ctime == S[n].ctime
2516 - S[n-1].ctime == S[n].ctime
2513 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
2517 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
2514 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
2518 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
2515 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
2519 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
2516 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
2520 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
2517
2521
2518 Case (*2) above means that a file was changed twice or more at
2522 Case (*2) above means that a file was changed twice or more at
2519 same time in sec (= S[n-1].ctime), and comparison of timestamp
2523 same time in sec (= S[n-1].ctime), and comparison of timestamp
2520 is ambiguous.
2524 is ambiguous.
2521
2525
2522 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
2526 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
2523 timestamp is ambiguous".
2527 timestamp is ambiguous".
2524
2528
2525 But advancing mtime only in case (*2) doesn't work as
2529 But advancing mtime only in case (*2) doesn't work as
2526 expected, because naturally advanced S[n].mtime in case (*1)
2530 expected, because naturally advanced S[n].mtime in case (*1)
2527 might be equal to manually advanced S[n-1 or earlier].mtime.
2531 might be equal to manually advanced S[n-1 or earlier].mtime.
2528
2532
2529 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
2533 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
2530 treated as ambiguous regardless of mtime, to avoid overlooking
2534 treated as ambiguous regardless of mtime, to avoid overlooking
2531 by confliction between such mtime.
2535 by confliction between such mtime.
2532
2536
2533 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
2537 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
2534 S[n].mtime", even if size of a file isn't changed.
2538 S[n].mtime", even if size of a file isn't changed.
2535 """
2539 """
2536 try:
2540 try:
2537 return self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME]
2541 return self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME]
2538 except AttributeError:
2542 except AttributeError:
2539 return False
2543 return False
2540
2544
2541 def avoidambig(self, path, old):
2545 def avoidambig(self, path, old):
2542 """Change file stat of specified path to avoid ambiguity
2546 """Change file stat of specified path to avoid ambiguity
2543
2547
2544 'old' should be previous filestat of 'path'.
2548 'old' should be previous filestat of 'path'.
2545
2549
2546 This skips avoiding ambiguity, if a process doesn't have
2550 This skips avoiding ambiguity, if a process doesn't have
2547 appropriate privileges for 'path'. This returns False in this
2551 appropriate privileges for 'path'. This returns False in this
2548 case.
2552 case.
2549
2553
2550 Otherwise, this returns True, as "ambiguity is avoided".
2554 Otherwise, this returns True, as "ambiguity is avoided".
2551 """
2555 """
2552 advanced = (old.stat[stat.ST_MTIME] + 1) & 0x7FFFFFFF
2556 advanced = (old.stat[stat.ST_MTIME] + 1) & 0x7FFFFFFF
2553 try:
2557 try:
2554 os.utime(path, (advanced, advanced))
2558 os.utime(path, (advanced, advanced))
2555 except PermissionError:
2559 except PermissionError:
2556 # utime() on the file created by another user causes EPERM,
2560 # utime() on the file created by another user causes EPERM,
2557 # if a process doesn't have appropriate privileges
2561 # if a process doesn't have appropriate privileges
2558 return False
2562 return False
2559 return True
2563 return True
2560
2564
2561 def __ne__(self, other):
2565 def __ne__(self, other):
2562 return not self == other
2566 return not self == other
2563
2567
2564
2568
2565 class atomictempfile:
2569 class atomictempfile:
2566 """writable file object that atomically updates a file
2570 """writable file object that atomically updates a file
2567
2571
2568 All writes will go to a temporary copy of the original file. Call
2572 All writes will go to a temporary copy of the original file. Call
2569 close() when you are done writing, and atomictempfile will rename
2573 close() when you are done writing, and atomictempfile will rename
2570 the temporary copy to the original name, making the changes
2574 the temporary copy to the original name, making the changes
2571 visible. If the object is destroyed without being closed, all your
2575 visible. If the object is destroyed without being closed, all your
2572 writes are discarded.
2576 writes are discarded.
2573
2577
2574 checkambig argument of constructor is used with filestat, and is
2578 checkambig argument of constructor is used with filestat, and is
2575 useful only if target file is guarded by any lock (e.g. repo.lock
2579 useful only if target file is guarded by any lock (e.g. repo.lock
2576 or repo.wlock).
2580 or repo.wlock).
2577 """
2581 """
2578
2582
2579 def __init__(self, name, mode=b'w+b', createmode=None, checkambig=False):
2583 def __init__(self, name, mode=b'w+b', createmode=None, checkambig=False):
2580 self.__name = name # permanent name
2584 self.__name = name # permanent name
2581 self._tempname = mktempcopy(
2585 self._tempname = mktempcopy(
2582 name,
2586 name,
2583 emptyok=(b'w' in mode),
2587 emptyok=(b'w' in mode),
2584 createmode=createmode,
2588 createmode=createmode,
2585 enforcewritable=(b'w' in mode),
2589 enforcewritable=(b'w' in mode),
2586 )
2590 )
2587
2591
2588 self._fp = posixfile(self._tempname, mode)
2592 self._fp = posixfile(self._tempname, mode)
2589 self._checkambig = checkambig
2593 self._checkambig = checkambig
2590
2594
2591 # delegated methods
2595 # delegated methods
2592 self.read = self._fp.read
2596 self.read = self._fp.read
2593 self.write = self._fp.write
2597 self.write = self._fp.write
2594 self.writelines = self._fp.writelines
2598 self.writelines = self._fp.writelines
2595 self.seek = self._fp.seek
2599 self.seek = self._fp.seek
2596 self.tell = self._fp.tell
2600 self.tell = self._fp.tell
2597 self.fileno = self._fp.fileno
2601 self.fileno = self._fp.fileno
2598
2602
2599 def close(self):
2603 def close(self):
2600 if not self._fp.closed:
2604 if not self._fp.closed:
2601 self._fp.close()
2605 self._fp.close()
2602 filename = localpath(self.__name)
2606 filename = localpath(self.__name)
2603 oldstat = self._checkambig and filestat.frompath(filename)
2607 oldstat = self._checkambig and filestat.frompath(filename)
2604 if oldstat and oldstat.stat:
2608 if oldstat and oldstat.stat:
2605 rename(self._tempname, filename)
2609 rename(self._tempname, filename)
2606 newstat = filestat.frompath(filename)
2610 newstat = filestat.frompath(filename)
2607 if newstat.isambig(oldstat):
2611 if newstat.isambig(oldstat):
2608 # stat of changed file is ambiguous to original one
2612 # stat of changed file is ambiguous to original one
2609 advanced = (oldstat.stat[stat.ST_MTIME] + 1) & 0x7FFFFFFF
2613 advanced = (oldstat.stat[stat.ST_MTIME] + 1) & 0x7FFFFFFF
2610 os.utime(filename, (advanced, advanced))
2614 os.utime(filename, (advanced, advanced))
2611 else:
2615 else:
2612 rename(self._tempname, filename)
2616 rename(self._tempname, filename)
2613
2617
2614 def discard(self):
2618 def discard(self):
2615 if not self._fp.closed:
2619 if not self._fp.closed:
2616 try:
2620 try:
2617 os.unlink(self._tempname)
2621 os.unlink(self._tempname)
2618 except OSError:
2622 except OSError:
2619 pass
2623 pass
2620 self._fp.close()
2624 self._fp.close()
2621
2625
2622 def __del__(self):
2626 def __del__(self):
2623 if hasattr(self, '_fp'): # constructor actually did something
2627 if hasattr(self, '_fp'): # constructor actually did something
2624 self.discard()
2628 self.discard()
2625
2629
2626 def __enter__(self):
2630 def __enter__(self):
2627 return self
2631 return self
2628
2632
2629 def __exit__(self, exctype, excvalue, traceback):
2633 def __exit__(self, exctype, excvalue, traceback):
2630 if exctype is not None:
2634 if exctype is not None:
2631 self.discard()
2635 self.discard()
2632 else:
2636 else:
2633 self.close()
2637 self.close()
2634
2638
2635
2639
2636 def tryrmdir(f):
2640 def tryrmdir(f):
2637 try:
2641 try:
2638 removedirs(f)
2642 removedirs(f)
2639 except OSError as e:
2643 except OSError as e:
2640 if e.errno != errno.ENOENT and e.errno != errno.ENOTEMPTY:
2644 if e.errno != errno.ENOENT and e.errno != errno.ENOTEMPTY:
2641 raise
2645 raise
2642
2646
2643
2647
2644 def unlinkpath(
2648 def unlinkpath(
2645 f: bytes, ignoremissing: bool = False, rmdir: bool = True
2649 f: bytes, ignoremissing: bool = False, rmdir: bool = True
2646 ) -> None:
2650 ) -> None:
2647 """unlink and remove the directory if it is empty"""
2651 """unlink and remove the directory if it is empty"""
2648 if ignoremissing:
2652 if ignoremissing:
2649 tryunlink(f)
2653 tryunlink(f)
2650 else:
2654 else:
2651 unlink(f)
2655 unlink(f)
2652 if rmdir:
2656 if rmdir:
2653 # try removing directories that might now be empty
2657 # try removing directories that might now be empty
2654 try:
2658 try:
2655 removedirs(os.path.dirname(f))
2659 removedirs(os.path.dirname(f))
2656 except OSError:
2660 except OSError:
2657 pass
2661 pass
2658
2662
2659
2663
2660 def tryunlink(f: bytes) -> bool:
2664 def tryunlink(f: bytes) -> bool:
2661 """Attempt to remove a file, ignoring FileNotFoundError.
2665 """Attempt to remove a file, ignoring FileNotFoundError.
2662
2666
2663 Returns False in case the file did not exit, True otherwise
2667 Returns False in case the file did not exit, True otherwise
2664 """
2668 """
2665 try:
2669 try:
2666 unlink(f)
2670 unlink(f)
2667 return True
2671 return True
2668 except FileNotFoundError:
2672 except FileNotFoundError:
2669 return False
2673 return False
2670
2674
2671
2675
2672 def makedirs(
2676 def makedirs(
2673 name: bytes, mode: Optional[int] = None, notindexed: bool = False
2677 name: bytes, mode: Optional[int] = None, notindexed: bool = False
2674 ) -> None:
2678 ) -> None:
2675 """recursive directory creation with parent mode inheritance
2679 """recursive directory creation with parent mode inheritance
2676
2680
2677 Newly created directories are marked as "not to be indexed by
2681 Newly created directories are marked as "not to be indexed by
2678 the content indexing service", if ``notindexed`` is specified
2682 the content indexing service", if ``notindexed`` is specified
2679 for "write" mode access.
2683 for "write" mode access.
2680 """
2684 """
2681 try:
2685 try:
2682 makedir(name, notindexed)
2686 makedir(name, notindexed)
2683 except OSError as err:
2687 except OSError as err:
2684 if err.errno == errno.EEXIST:
2688 if err.errno == errno.EEXIST:
2685 return
2689 return
2686 if err.errno != errno.ENOENT or not name:
2690 if err.errno != errno.ENOENT or not name:
2687 raise
2691 raise
2688 parent = os.path.dirname(abspath(name))
2692 parent = os.path.dirname(abspath(name))
2689 if parent == name:
2693 if parent == name:
2690 raise
2694 raise
2691 makedirs(parent, mode, notindexed)
2695 makedirs(parent, mode, notindexed)
2692 try:
2696 try:
2693 makedir(name, notindexed)
2697 makedir(name, notindexed)
2694 except OSError as err:
2698 except OSError as err:
2695 # Catch EEXIST to handle races
2699 # Catch EEXIST to handle races
2696 if err.errno == errno.EEXIST:
2700 if err.errno == errno.EEXIST:
2697 return
2701 return
2698 raise
2702 raise
2699 if mode is not None:
2703 if mode is not None:
2700 os.chmod(name, mode)
2704 os.chmod(name, mode)
2701
2705
2702
2706
2703 def readfile(path: bytes) -> bytes:
2707 def readfile(path: bytes) -> bytes:
2704 with open(path, b'rb') as fp:
2708 with open(path, b'rb') as fp:
2705 return fp.read()
2709 return fp.read()
2706
2710
2707
2711
2708 def writefile(path: bytes, text: bytes) -> None:
2712 def writefile(path: bytes, text: bytes) -> None:
2709 with open(path, b'wb') as fp:
2713 with open(path, b'wb') as fp:
2710 fp.write(text)
2714 fp.write(text)
2711
2715
2712
2716
2713 def appendfile(path: bytes, text: bytes) -> None:
2717 def appendfile(path: bytes, text: bytes) -> None:
2714 with open(path, b'ab') as fp:
2718 with open(path, b'ab') as fp:
2715 fp.write(text)
2719 fp.write(text)
2716
2720
2717
2721
2718 class chunkbuffer:
2722 class chunkbuffer:
2719 """Allow arbitrary sized chunks of data to be efficiently read from an
2723 """Allow arbitrary sized chunks of data to be efficiently read from an
2720 iterator over chunks of arbitrary size."""
2724 iterator over chunks of arbitrary size."""
2721
2725
2722 def __init__(self, in_iter):
2726 def __init__(self, in_iter):
2723 """in_iter is the iterator that's iterating over the input chunks."""
2727 """in_iter is the iterator that's iterating over the input chunks."""
2724
2728
2725 def splitbig(chunks):
2729 def splitbig(chunks):
2726 for chunk in chunks:
2730 for chunk in chunks:
2727 if len(chunk) > 2**20:
2731 if len(chunk) > 2**20:
2728 pos = 0
2732 pos = 0
2729 while pos < len(chunk):
2733 while pos < len(chunk):
2730 end = pos + 2**18
2734 end = pos + 2**18
2731 yield chunk[pos:end]
2735 yield chunk[pos:end]
2732 pos = end
2736 pos = end
2733 else:
2737 else:
2734 yield chunk
2738 yield chunk
2735
2739
2736 self.iter = splitbig(in_iter)
2740 self.iter = splitbig(in_iter)
2737 self._queue = collections.deque()
2741 self._queue = collections.deque()
2738 self._chunkoffset = 0
2742 self._chunkoffset = 0
2739
2743
2740 def read(self, l=None):
2744 def read(self, l=None):
2741 """Read L bytes of data from the iterator of chunks of data.
2745 """Read L bytes of data from the iterator of chunks of data.
2742 Returns less than L bytes if the iterator runs dry.
2746 Returns less than L bytes if the iterator runs dry.
2743
2747
2744 If size parameter is omitted, read everything"""
2748 If size parameter is omitted, read everything"""
2745 if l is None:
2749 if l is None:
2746 return b''.join(self.iter)
2750 return b''.join(self.iter)
2747
2751
2748 left = l
2752 left = l
2749 buf = []
2753 buf = []
2750 queue = self._queue
2754 queue = self._queue
2751 while left > 0:
2755 while left > 0:
2752 # refill the queue
2756 # refill the queue
2753 if not queue:
2757 if not queue:
2754 target = 2**18
2758 target = 2**18
2755 for chunk in self.iter:
2759 for chunk in self.iter:
2756 queue.append(chunk)
2760 queue.append(chunk)
2757 target -= len(chunk)
2761 target -= len(chunk)
2758 if target <= 0:
2762 if target <= 0:
2759 break
2763 break
2760 if not queue:
2764 if not queue:
2761 break
2765 break
2762
2766
2763 # The easy way to do this would be to queue.popleft(), modify the
2767 # The easy way to do this would be to queue.popleft(), modify the
2764 # chunk (if necessary), then queue.appendleft(). However, for cases
2768 # chunk (if necessary), then queue.appendleft(). However, for cases
2765 # where we read partial chunk content, this incurs 2 dequeue
2769 # where we read partial chunk content, this incurs 2 dequeue
2766 # mutations and creates a new str for the remaining chunk in the
2770 # mutations and creates a new str for the remaining chunk in the
2767 # queue. Our code below avoids this overhead.
2771 # queue. Our code below avoids this overhead.
2768
2772
2769 chunk = queue[0]
2773 chunk = queue[0]
2770 chunkl = len(chunk)
2774 chunkl = len(chunk)
2771 offset = self._chunkoffset
2775 offset = self._chunkoffset
2772
2776
2773 # Use full chunk.
2777 # Use full chunk.
2774 if offset == 0 and left >= chunkl:
2778 if offset == 0 and left >= chunkl:
2775 left -= chunkl
2779 left -= chunkl
2776 queue.popleft()
2780 queue.popleft()
2777 buf.append(chunk)
2781 buf.append(chunk)
2778 # self._chunkoffset remains at 0.
2782 # self._chunkoffset remains at 0.
2779 continue
2783 continue
2780
2784
2781 chunkremaining = chunkl - offset
2785 chunkremaining = chunkl - offset
2782
2786
2783 # Use all of unconsumed part of chunk.
2787 # Use all of unconsumed part of chunk.
2784 if left >= chunkremaining:
2788 if left >= chunkremaining:
2785 left -= chunkremaining
2789 left -= chunkremaining
2786 queue.popleft()
2790 queue.popleft()
2787 # offset == 0 is enabled by block above, so this won't merely
2791 # offset == 0 is enabled by block above, so this won't merely
2788 # copy via ``chunk[0:]``.
2792 # copy via ``chunk[0:]``.
2789 buf.append(chunk[offset:])
2793 buf.append(chunk[offset:])
2790 self._chunkoffset = 0
2794 self._chunkoffset = 0
2791
2795
2792 # Partial chunk needed.
2796 # Partial chunk needed.
2793 else:
2797 else:
2794 buf.append(chunk[offset : offset + left])
2798 buf.append(chunk[offset : offset + left])
2795 self._chunkoffset += left
2799 self._chunkoffset += left
2796 left -= chunkremaining
2800 left -= chunkremaining
2797
2801
2798 return b''.join(buf)
2802 return b''.join(buf)
2799
2803
2800
2804
2801 def filechunkiter(f, size=131072, limit=None):
2805 def filechunkiter(f, size=131072, limit=None):
2802 """Create a generator that produces the data in the file size
2806 """Create a generator that produces the data in the file size
2803 (default 131072) bytes at a time, up to optional limit (default is
2807 (default 131072) bytes at a time, up to optional limit (default is
2804 to read all data). Chunks may be less than size bytes if the
2808 to read all data). Chunks may be less than size bytes if the
2805 chunk is the last chunk in the file, or the file is a socket or
2809 chunk is the last chunk in the file, or the file is a socket or
2806 some other type of file that sometimes reads less data than is
2810 some other type of file that sometimes reads less data than is
2807 requested."""
2811 requested."""
2808 assert size >= 0
2812 assert size >= 0
2809 assert limit is None or limit >= 0
2813 assert limit is None or limit >= 0
2810 while True:
2814 while True:
2811 if limit is None:
2815 if limit is None:
2812 nbytes = size
2816 nbytes = size
2813 else:
2817 else:
2814 nbytes = min(limit, size)
2818 nbytes = min(limit, size)
2815 s = nbytes and f.read(nbytes)
2819 s = nbytes and f.read(nbytes)
2816 if not s:
2820 if not s:
2817 break
2821 break
2818 if limit:
2822 if limit:
2819 limit -= len(s)
2823 limit -= len(s)
2820 yield s
2824 yield s
2821
2825
2822
2826
2823 class cappedreader:
2827 class cappedreader:
2824 """A file object proxy that allows reading up to N bytes.
2828 """A file object proxy that allows reading up to N bytes.
2825
2829
2826 Given a source file object, instances of this type allow reading up to
2830 Given a source file object, instances of this type allow reading up to
2827 N bytes from that source file object. Attempts to read past the allowed
2831 N bytes from that source file object. Attempts to read past the allowed
2828 limit are treated as EOF.
2832 limit are treated as EOF.
2829
2833
2830 It is assumed that I/O is not performed on the original file object
2834 It is assumed that I/O is not performed on the original file object
2831 in addition to I/O that is performed by this instance. If there is,
2835 in addition to I/O that is performed by this instance. If there is,
2832 state tracking will get out of sync and unexpected results will ensue.
2836 state tracking will get out of sync and unexpected results will ensue.
2833 """
2837 """
2834
2838
2835 def __init__(self, fh, limit):
2839 def __init__(self, fh, limit):
2836 """Allow reading up to <limit> bytes from <fh>."""
2840 """Allow reading up to <limit> bytes from <fh>."""
2837 self._fh = fh
2841 self._fh = fh
2838 self._left = limit
2842 self._left = limit
2839
2843
2840 def read(self, n=-1):
2844 def read(self, n=-1):
2841 if not self._left:
2845 if not self._left:
2842 return b''
2846 return b''
2843
2847
2844 if n < 0:
2848 if n < 0:
2845 n = self._left
2849 n = self._left
2846
2850
2847 data = self._fh.read(min(n, self._left))
2851 data = self._fh.read(min(n, self._left))
2848 self._left -= len(data)
2852 self._left -= len(data)
2849 assert self._left >= 0
2853 assert self._left >= 0
2850
2854
2851 return data
2855 return data
2852
2856
2853 def readinto(self, b):
2857 def readinto(self, b):
2854 res = self.read(len(b))
2858 res = self.read(len(b))
2855 if res is None:
2859 if res is None:
2856 return None
2860 return None
2857
2861
2858 b[0 : len(res)] = res
2862 b[0 : len(res)] = res
2859 return len(res)
2863 return len(res)
2860
2864
2861
2865
2862 def unitcountfn(*unittable):
2866 def unitcountfn(*unittable):
2863 '''return a function that renders a readable count of some quantity'''
2867 '''return a function that renders a readable count of some quantity'''
2864
2868
2865 def go(count):
2869 def go(count):
2866 for multiplier, divisor, format in unittable:
2870 for multiplier, divisor, format in unittable:
2867 if abs(count) >= divisor * multiplier:
2871 if abs(count) >= divisor * multiplier:
2868 return format % (count / float(divisor))
2872 return format % (count / float(divisor))
2869 return unittable[-1][2] % count
2873 return unittable[-1][2] % count
2870
2874
2871 return go
2875 return go
2872
2876
2873
2877
2874 def processlinerange(fromline: int, toline: int) -> Tuple[int, int]:
2878 def processlinerange(fromline: int, toline: int) -> Tuple[int, int]:
2875 """Check that linerange <fromline>:<toline> makes sense and return a
2879 """Check that linerange <fromline>:<toline> makes sense and return a
2876 0-based range.
2880 0-based range.
2877
2881
2878 >>> processlinerange(10, 20)
2882 >>> processlinerange(10, 20)
2879 (9, 20)
2883 (9, 20)
2880 >>> processlinerange(2, 1)
2884 >>> processlinerange(2, 1)
2881 Traceback (most recent call last):
2885 Traceback (most recent call last):
2882 ...
2886 ...
2883 ParseError: line range must be positive
2887 ParseError: line range must be positive
2884 >>> processlinerange(0, 5)
2888 >>> processlinerange(0, 5)
2885 Traceback (most recent call last):
2889 Traceback (most recent call last):
2886 ...
2890 ...
2887 ParseError: fromline must be strictly positive
2891 ParseError: fromline must be strictly positive
2888 """
2892 """
2889 if toline - fromline < 0:
2893 if toline - fromline < 0:
2890 raise error.ParseError(_(b"line range must be positive"))
2894 raise error.ParseError(_(b"line range must be positive"))
2891 if fromline < 1:
2895 if fromline < 1:
2892 raise error.ParseError(_(b"fromline must be strictly positive"))
2896 raise error.ParseError(_(b"fromline must be strictly positive"))
2893 return fromline - 1, toline
2897 return fromline - 1, toline
2894
2898
2895
2899
2896 bytecount = unitcountfn(
2900 bytecount = unitcountfn(
2897 (100, 1 << 30, _(b'%.0f GB')),
2901 (100, 1 << 30, _(b'%.0f GB')),
2898 (10, 1 << 30, _(b'%.1f GB')),
2902 (10, 1 << 30, _(b'%.1f GB')),
2899 (1, 1 << 30, _(b'%.2f GB')),
2903 (1, 1 << 30, _(b'%.2f GB')),
2900 (100, 1 << 20, _(b'%.0f MB')),
2904 (100, 1 << 20, _(b'%.0f MB')),
2901 (10, 1 << 20, _(b'%.1f MB')),
2905 (10, 1 << 20, _(b'%.1f MB')),
2902 (1, 1 << 20, _(b'%.2f MB')),
2906 (1, 1 << 20, _(b'%.2f MB')),
2903 (100, 1 << 10, _(b'%.0f KB')),
2907 (100, 1 << 10, _(b'%.0f KB')),
2904 (10, 1 << 10, _(b'%.1f KB')),
2908 (10, 1 << 10, _(b'%.1f KB')),
2905 (1, 1 << 10, _(b'%.2f KB')),
2909 (1, 1 << 10, _(b'%.2f KB')),
2906 (1, 1, _(b'%.0f bytes')),
2910 (1, 1, _(b'%.0f bytes')),
2907 )
2911 )
2908
2912
2909
2913
2910 class transformingwriter:
2914 class transformingwriter(typelib.BinaryIO_Proxy):
2911 """Writable file wrapper to transform data by function"""
2915 """Writable file wrapper to transform data by function"""
2912
2916
2913 def __init__(self, fp, encode):
2917 def __init__(self, fp: BinaryIO, encode: Callable[[bytes], bytes]) -> None:
2914 self._fp = fp
2918 self._fp = fp
2915 self._encode = encode
2919 self._encode = encode
2916
2920
2917 def close(self):
2921 def close(self) -> None:
2918 self._fp.close()
2922 self._fp.close()
2919
2923
2920 def flush(self):
2924 def flush(self) -> None:
2921 self._fp.flush()
2925 self._fp.flush()
2922
2926
2923 def write(self, data):
2927 def write(self, data: bytes) -> int:
2924 return self._fp.write(self._encode(data))
2928 return self._fp.write(self._encode(data))
2925
2929
2926
2930
2927 # Matches a single EOL which can either be a CRLF where repeated CR
2931 # Matches a single EOL which can either be a CRLF where repeated CR
2928 # are removed or a LF. We do not care about old Macintosh files, so a
2932 # are removed or a LF. We do not care about old Macintosh files, so a
2929 # stray CR is an error.
2933 # stray CR is an error.
2930 _eolre = remod.compile(br'\r*\n')
2934 _eolre = remod.compile(br'\r*\n')
2931
2935
2932
2936
2933 def tolf(s: bytes) -> bytes:
2937 def tolf(s: bytes) -> bytes:
2934 return _eolre.sub(b'\n', s)
2938 return _eolre.sub(b'\n', s)
2935
2939
2936
2940
2937 def tocrlf(s: bytes) -> bytes:
2941 def tocrlf(s: bytes) -> bytes:
2938 return _eolre.sub(b'\r\n', s)
2942 return _eolre.sub(b'\r\n', s)
2939
2943
2940
2944
2941 def _crlfwriter(fp):
2945 def _crlfwriter(fp: typelib.BinaryIO_Proxy) -> typelib.BinaryIO_Proxy:
2942 return transformingwriter(fp, tocrlf)
2946 return transformingwriter(fp, tocrlf)
2943
2947
2944
2948
2945 if pycompat.oslinesep == b'\r\n':
2949 if pycompat.oslinesep == b'\r\n':
2946 tonativeeol = tocrlf
2950 tonativeeol = tocrlf
2947 fromnativeeol = tolf
2951 fromnativeeol = tolf
2948 nativeeolwriter = _crlfwriter
2952 nativeeolwriter = _crlfwriter
2949 else:
2953 else:
2950 tonativeeol = pycompat.identity
2954 tonativeeol = pycompat.identity
2951 fromnativeeol = pycompat.identity
2955 fromnativeeol = pycompat.identity
2952 nativeeolwriter = pycompat.identity
2956 nativeeolwriter = pycompat.identity
2953
2957
2958 if typing.TYPE_CHECKING:
2959 # Replace the various overloads that come along with aliasing other methods
2960 # with the narrow definition that we care about in the type checking phase
2961 # only. This ensures that both Windows and POSIX see only the definition
2962 # that is actually available.
2963
2964 def tonativeeol(s: bytes) -> bytes:
2965 raise NotImplementedError
2966
2967 def fromnativeeol(s: bytes) -> bytes:
2968 raise NotImplementedError
2969
2970 def nativeeolwriter(fp: typelib.BinaryIO_Proxy) -> typelib.BinaryIO_Proxy:
2971 raise NotImplementedError
2972
2954
2973
2955 # TODO delete since workaround variant for Python 2 no longer needed.
2974 # TODO delete since workaround variant for Python 2 no longer needed.
2956 def iterfile(fp):
2975 def iterfile(fp):
2957 return fp
2976 return fp
2958
2977
2959
2978
2960 def iterlines(iterator: Iterable[bytes]) -> Iterator[bytes]:
2979 def iterlines(iterator: Iterable[bytes]) -> Iterator[bytes]:
2961 for chunk in iterator:
2980 for chunk in iterator:
2962 for line in chunk.splitlines():
2981 for line in chunk.splitlines():
2963 yield line
2982 yield line
2964
2983
2965
2984
2966 def expandpath(path: bytes) -> bytes:
2985 def expandpath(path: bytes) -> bytes:
2967 return os.path.expanduser(os.path.expandvars(path))
2986 return os.path.expanduser(os.path.expandvars(path))
2968
2987
2969
2988
2970 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2989 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2971 """Return the result of interpolating items in the mapping into string s.
2990 """Return the result of interpolating items in the mapping into string s.
2972
2991
2973 prefix is a single character string, or a two character string with
2992 prefix is a single character string, or a two character string with
2974 a backslash as the first character if the prefix needs to be escaped in
2993 a backslash as the first character if the prefix needs to be escaped in
2975 a regular expression.
2994 a regular expression.
2976
2995
2977 fn is an optional function that will be applied to the replacement text
2996 fn is an optional function that will be applied to the replacement text
2978 just before replacement.
2997 just before replacement.
2979
2998
2980 escape_prefix is an optional flag that allows using doubled prefix for
2999 escape_prefix is an optional flag that allows using doubled prefix for
2981 its escaping.
3000 its escaping.
2982 """
3001 """
2983 fn = fn or (lambda s: s)
3002 fn = fn or (lambda s: s)
2984 patterns = b'|'.join(mapping.keys())
3003 patterns = b'|'.join(mapping.keys())
2985 if escape_prefix:
3004 if escape_prefix:
2986 patterns += b'|' + prefix
3005 patterns += b'|' + prefix
2987 if len(prefix) > 1:
3006 if len(prefix) > 1:
2988 prefix_char = prefix[1:]
3007 prefix_char = prefix[1:]
2989 else:
3008 else:
2990 prefix_char = prefix
3009 prefix_char = prefix
2991 mapping[prefix_char] = prefix_char
3010 mapping[prefix_char] = prefix_char
2992 r = remod.compile(br'%s(%s)' % (prefix, patterns))
3011 r = remod.compile(br'%s(%s)' % (prefix, patterns))
2993 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
3012 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2994
3013
2995
3014
2996 timecount = unitcountfn(
3015 timecount = unitcountfn(
2997 (1, 1e3, _(b'%.0f s')),
3016 (1, 1e3, _(b'%.0f s')),
2998 (100, 1, _(b'%.1f s')),
3017 (100, 1, _(b'%.1f s')),
2999 (10, 1, _(b'%.2f s')),
3018 (10, 1, _(b'%.2f s')),
3000 (1, 1, _(b'%.3f s')),
3019 (1, 1, _(b'%.3f s')),
3001 (100, 0.001, _(b'%.1f ms')),
3020 (100, 0.001, _(b'%.1f ms')),
3002 (10, 0.001, _(b'%.2f ms')),
3021 (10, 0.001, _(b'%.2f ms')),
3003 (1, 0.001, _(b'%.3f ms')),
3022 (1, 0.001, _(b'%.3f ms')),
3004 (100, 0.000001, _(b'%.1f us')),
3023 (100, 0.000001, _(b'%.1f us')),
3005 (10, 0.000001, _(b'%.2f us')),
3024 (10, 0.000001, _(b'%.2f us')),
3006 (1, 0.000001, _(b'%.3f us')),
3025 (1, 0.000001, _(b'%.3f us')),
3007 (100, 0.000000001, _(b'%.1f ns')),
3026 (100, 0.000000001, _(b'%.1f ns')),
3008 (10, 0.000000001, _(b'%.2f ns')),
3027 (10, 0.000000001, _(b'%.2f ns')),
3009 (1, 0.000000001, _(b'%.3f ns')),
3028 (1, 0.000000001, _(b'%.3f ns')),
3010 )
3029 )
3011
3030
3012
3031
3013 @attr.s
3032 @attr.s
3014 class timedcmstats:
3033 class timedcmstats:
3015 """Stats information produced by the timedcm context manager on entering."""
3034 """Stats information produced by the timedcm context manager on entering."""
3016
3035
3017 # the starting value of the timer as a float (meaning and resulution is
3036 # the starting value of the timer as a float (meaning and resulution is
3018 # platform dependent, see util.timer)
3037 # platform dependent, see util.timer)
3019 start = attr.ib(default=attr.Factory(lambda: timer()))
3038 start = attr.ib(default=attr.Factory(lambda: timer()))
3020 # the number of seconds as a floating point value; starts at 0, updated when
3039 # the number of seconds as a floating point value; starts at 0, updated when
3021 # the context is exited.
3040 # the context is exited.
3022 elapsed = attr.ib(default=0)
3041 elapsed = attr.ib(default=0)
3023 # the number of nested timedcm context managers.
3042 # the number of nested timedcm context managers.
3024 level = attr.ib(default=1)
3043 level = attr.ib(default=1)
3025
3044
3026 def __bytes__(self):
3045 def __bytes__(self):
3027 return timecount(self.elapsed) if self.elapsed else b'<unknown>'
3046 return timecount(self.elapsed) if self.elapsed else b'<unknown>'
3028
3047
3029 __str__ = encoding.strmethod(__bytes__)
3048 __str__ = encoding.strmethod(__bytes__)
3030
3049
3031
3050
3032 @contextlib.contextmanager
3051 @contextlib.contextmanager
3033 def timedcm(whencefmt, *whenceargs):
3052 def timedcm(whencefmt, *whenceargs):
3034 """A context manager that produces timing information for a given context.
3053 """A context manager that produces timing information for a given context.
3035
3054
3036 On entering a timedcmstats instance is produced.
3055 On entering a timedcmstats instance is produced.
3037
3056
3038 This context manager is reentrant.
3057 This context manager is reentrant.
3039
3058
3040 """
3059 """
3041 # track nested context managers
3060 # track nested context managers
3042 timedcm._nested += 1
3061 timedcm._nested += 1
3043 timing_stats = timedcmstats(level=timedcm._nested)
3062 timing_stats = timedcmstats(level=timedcm._nested)
3044 try:
3063 try:
3045 with tracing.log(whencefmt, *whenceargs):
3064 with tracing.log(whencefmt, *whenceargs):
3046 yield timing_stats
3065 yield timing_stats
3047 finally:
3066 finally:
3048 timing_stats.elapsed = timer() - timing_stats.start
3067 timing_stats.elapsed = timer() - timing_stats.start
3049 timedcm._nested -= 1
3068 timedcm._nested -= 1
3050
3069
3051
3070
3052 timedcm._nested = 0
3071 timedcm._nested = 0
3053
3072
3054
3073
3055 def timed(func):
3074 def timed(func):
3056 """Report the execution time of a function call to stderr.
3075 """Report the execution time of a function call to stderr.
3057
3076
3058 During development, use as a decorator when you need to measure
3077 During development, use as a decorator when you need to measure
3059 the cost of a function, e.g. as follows:
3078 the cost of a function, e.g. as follows:
3060
3079
3061 @util.timed
3080 @util.timed
3062 def foo(a, b, c):
3081 def foo(a, b, c):
3063 pass
3082 pass
3064 """
3083 """
3065
3084
3066 def wrapper(*args, **kwargs):
3085 def wrapper(*args, **kwargs):
3067 with timedcm(pycompat.bytestr(func.__name__)) as time_stats:
3086 with timedcm(pycompat.bytestr(func.__name__)) as time_stats:
3068 result = func(*args, **kwargs)
3087 result = func(*args, **kwargs)
3069 stderr = procutil.stderr
3088 stderr = procutil.stderr
3070 stderr.write(
3089 stderr.write(
3071 b'%s%s: %s\n'
3090 b'%s%s: %s\n'
3072 % (
3091 % (
3073 b' ' * time_stats.level * 2,
3092 b' ' * time_stats.level * 2,
3074 pycompat.bytestr(func.__name__),
3093 pycompat.bytestr(func.__name__),
3075 time_stats,
3094 time_stats,
3076 )
3095 )
3077 )
3096 )
3078 return result
3097 return result
3079
3098
3080 return wrapper
3099 return wrapper
3081
3100
3082
3101
3083 _sizeunits = (
3102 _sizeunits = (
3084 (b'm', 2**20),
3103 (b'm', 2**20),
3085 (b'k', 2**10),
3104 (b'k', 2**10),
3086 (b'g', 2**30),
3105 (b'g', 2**30),
3087 (b'kb', 2**10),
3106 (b'kb', 2**10),
3088 (b'mb', 2**20),
3107 (b'mb', 2**20),
3089 (b'gb', 2**30),
3108 (b'gb', 2**30),
3090 (b'b', 1),
3109 (b'b', 1),
3091 )
3110 )
3092
3111
3093
3112
3094 def sizetoint(s: bytes) -> int:
3113 def sizetoint(s: bytes) -> int:
3095 """Convert a space specifier to a byte count.
3114 """Convert a space specifier to a byte count.
3096
3115
3097 >>> sizetoint(b'30')
3116 >>> sizetoint(b'30')
3098 30
3117 30
3099 >>> sizetoint(b'2.2kb')
3118 >>> sizetoint(b'2.2kb')
3100 2252
3119 2252
3101 >>> sizetoint(b'6M')
3120 >>> sizetoint(b'6M')
3102 6291456
3121 6291456
3103 """
3122 """
3104 t = s.strip().lower()
3123 t = s.strip().lower()
3105 try:
3124 try:
3106 for k, u in _sizeunits:
3125 for k, u in _sizeunits:
3107 if t.endswith(k):
3126 if t.endswith(k):
3108 return int(float(t[: -len(k)]) * u)
3127 return int(float(t[: -len(k)]) * u)
3109 return int(t)
3128 return int(t)
3110 except ValueError:
3129 except ValueError:
3111 raise error.ParseError(_(b"couldn't parse size: %s") % s)
3130 raise error.ParseError(_(b"couldn't parse size: %s") % s)
3112
3131
3113
3132
3114 class hooks:
3133 class hooks:
3115 """A collection of hook functions that can be used to extend a
3134 """A collection of hook functions that can be used to extend a
3116 function's behavior. Hooks are called in lexicographic order,
3135 function's behavior. Hooks are called in lexicographic order,
3117 based on the names of their sources."""
3136 based on the names of their sources."""
3118
3137
3119 def __init__(self):
3138 def __init__(self):
3120 self._hooks = []
3139 self._hooks = []
3121
3140
3122 def add(self, source, hook):
3141 def add(self, source, hook):
3123 self._hooks.append((source, hook))
3142 self._hooks.append((source, hook))
3124
3143
3125 def __call__(self, *args):
3144 def __call__(self, *args):
3126 self._hooks.sort(key=lambda x: x[0])
3145 self._hooks.sort(key=lambda x: x[0])
3127 results = []
3146 results = []
3128 for source, hook in self._hooks:
3147 for source, hook in self._hooks:
3129 results.append(hook(*args))
3148 results.append(hook(*args))
3130 return results
3149 return results
3131
3150
3132
3151
3133 def getstackframes(skip=0, line=b' %-*s in %s\n', fileline=b'%s:%d', depth=0):
3152 def getstackframes(skip=0, line=b' %-*s in %s\n', fileline=b'%s:%d', depth=0):
3134 """Yields lines for a nicely formatted stacktrace.
3153 """Yields lines for a nicely formatted stacktrace.
3135 Skips the 'skip' last entries, then return the last 'depth' entries.
3154 Skips the 'skip' last entries, then return the last 'depth' entries.
3136 Each file+linenumber is formatted according to fileline.
3155 Each file+linenumber is formatted according to fileline.
3137 Each line is formatted according to line.
3156 Each line is formatted according to line.
3138 If line is None, it yields:
3157 If line is None, it yields:
3139 length of longest filepath+line number,
3158 length of longest filepath+line number,
3140 filepath+linenumber,
3159 filepath+linenumber,
3141 function
3160 function
3142
3161
3143 Not be used in production code but very convenient while developing.
3162 Not be used in production code but very convenient while developing.
3144 """
3163 """
3145 entries = [
3164 entries = [
3146 (fileline % (pycompat.sysbytes(fn), ln), pycompat.sysbytes(func))
3165 (fileline % (pycompat.sysbytes(fn), ln), pycompat.sysbytes(func))
3147 for fn, ln, func, _text in traceback.extract_stack()[: -skip - 1]
3166 for fn, ln, func, _text in traceback.extract_stack()[: -skip - 1]
3148 ][-depth:]
3167 ][-depth:]
3149 if entries:
3168 if entries:
3150 fnmax = max(len(entry[0]) for entry in entries)
3169 fnmax = max(len(entry[0]) for entry in entries)
3151 for fnln, func in entries:
3170 for fnln, func in entries:
3152 if line is None:
3171 if line is None:
3153 yield (fnmax, fnln, func)
3172 yield (fnmax, fnln, func)
3154 else:
3173 else:
3155 yield line % (fnmax, fnln, func)
3174 yield line % (fnmax, fnln, func)
3156
3175
3157
3176
3158 def debugstacktrace(
3177 def debugstacktrace(
3159 msg=b'stacktrace',
3178 msg=b'stacktrace',
3160 skip=0,
3179 skip=0,
3161 f=procutil.stderr,
3180 f=procutil.stderr,
3162 otherf=procutil.stdout,
3181 otherf=procutil.stdout,
3163 depth=0,
3182 depth=0,
3164 prefix=b'',
3183 prefix=b'',
3165 ):
3184 ):
3166 """Writes a message to f (stderr) with a nicely formatted stacktrace.
3185 """Writes a message to f (stderr) with a nicely formatted stacktrace.
3167 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3186 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3168 By default it will flush stdout first.
3187 By default it will flush stdout first.
3169 It can be used everywhere and intentionally does not require an ui object.
3188 It can be used everywhere and intentionally does not require an ui object.
3170 Not be used in production code but very convenient while developing.
3189 Not be used in production code but very convenient while developing.
3171 """
3190 """
3172 if otherf:
3191 if otherf:
3173 otherf.flush()
3192 otherf.flush()
3174 f.write(b'%s%s at:\n' % (prefix, msg.rstrip()))
3193 f.write(b'%s%s at:\n' % (prefix, msg.rstrip()))
3175 for line in getstackframes(skip + 1, depth=depth):
3194 for line in getstackframes(skip + 1, depth=depth):
3176 f.write(prefix + line)
3195 f.write(prefix + line)
3177 f.flush()
3196 f.flush()
3178
3197
3179
3198
3180 # convenient shortcut
3199 # convenient shortcut
3181 dst = debugstacktrace
3200 dst = debugstacktrace
3182
3201
3183
3202
3184 def safename(f, tag, ctx, others=None):
3203 def safename(f, tag, ctx, others=None):
3185 """
3204 """
3186 Generate a name that it is safe to rename f to in the given context.
3205 Generate a name that it is safe to rename f to in the given context.
3187
3206
3188 f: filename to rename
3207 f: filename to rename
3189 tag: a string tag that will be included in the new name
3208 tag: a string tag that will be included in the new name
3190 ctx: a context, in which the new name must not exist
3209 ctx: a context, in which the new name must not exist
3191 others: a set of other filenames that the new name must not be in
3210 others: a set of other filenames that the new name must not be in
3192
3211
3193 Returns a file name of the form oldname~tag[~number] which does not exist
3212 Returns a file name of the form oldname~tag[~number] which does not exist
3194 in the provided context and is not in the set of other names.
3213 in the provided context and is not in the set of other names.
3195 """
3214 """
3196 if others is None:
3215 if others is None:
3197 others = set()
3216 others = set()
3198
3217
3199 fn = b'%s~%s' % (f, tag)
3218 fn = b'%s~%s' % (f, tag)
3200 if fn not in ctx and fn not in others:
3219 if fn not in ctx and fn not in others:
3201 return fn
3220 return fn
3202 for n in itertools.count(1):
3221 for n in itertools.count(1):
3203 fn = b'%s~%s~%s' % (f, tag, n)
3222 fn = b'%s~%s~%s' % (f, tag, n)
3204 if fn not in ctx and fn not in others:
3223 if fn not in ctx and fn not in others:
3205 return fn
3224 return fn
3206
3225
3207
3226
3208 def readexactly(stream, n):
3227 def readexactly(stream, n):
3209 '''read n bytes from stream.read and abort if less was available'''
3228 '''read n bytes from stream.read and abort if less was available'''
3210 s = stream.read(n)
3229 s = stream.read(n)
3211 if len(s) < n:
3230 if len(s) < n:
3212 raise error.Abort(
3231 raise error.Abort(
3213 _(b"stream ended unexpectedly (got %d bytes, expected %d)")
3232 _(b"stream ended unexpectedly (got %d bytes, expected %d)")
3214 % (len(s), n)
3233 % (len(s), n)
3215 )
3234 )
3216 return s
3235 return s
3217
3236
3218
3237
3219 def uvarintencode(value):
3238 def uvarintencode(value):
3220 """Encode an unsigned integer value to a varint.
3239 """Encode an unsigned integer value to a varint.
3221
3240
3222 A varint is a variable length integer of 1 or more bytes. Each byte
3241 A varint is a variable length integer of 1 or more bytes. Each byte
3223 except the last has the most significant bit set. The lower 7 bits of
3242 except the last has the most significant bit set. The lower 7 bits of
3224 each byte store the 2's complement representation, least significant group
3243 each byte store the 2's complement representation, least significant group
3225 first.
3244 first.
3226
3245
3227 >>> uvarintencode(0)
3246 >>> uvarintencode(0)
3228 '\\x00'
3247 '\\x00'
3229 >>> uvarintencode(1)
3248 >>> uvarintencode(1)
3230 '\\x01'
3249 '\\x01'
3231 >>> uvarintencode(127)
3250 >>> uvarintencode(127)
3232 '\\x7f'
3251 '\\x7f'
3233 >>> uvarintencode(1337)
3252 >>> uvarintencode(1337)
3234 '\\xb9\\n'
3253 '\\xb9\\n'
3235 >>> uvarintencode(65536)
3254 >>> uvarintencode(65536)
3236 '\\x80\\x80\\x04'
3255 '\\x80\\x80\\x04'
3237 >>> uvarintencode(-1)
3256 >>> uvarintencode(-1)
3238 Traceback (most recent call last):
3257 Traceback (most recent call last):
3239 ...
3258 ...
3240 ProgrammingError: negative value for uvarint: -1
3259 ProgrammingError: negative value for uvarint: -1
3241 """
3260 """
3242 if value < 0:
3261 if value < 0:
3243 raise error.ProgrammingError(b'negative value for uvarint: %d' % value)
3262 raise error.ProgrammingError(b'negative value for uvarint: %d' % value)
3244 bits = value & 0x7F
3263 bits = value & 0x7F
3245 value >>= 7
3264 value >>= 7
3246 bytes = []
3265 bytes = []
3247 while value:
3266 while value:
3248 bytes.append(pycompat.bytechr(0x80 | bits))
3267 bytes.append(pycompat.bytechr(0x80 | bits))
3249 bits = value & 0x7F
3268 bits = value & 0x7F
3250 value >>= 7
3269 value >>= 7
3251 bytes.append(pycompat.bytechr(bits))
3270 bytes.append(pycompat.bytechr(bits))
3252
3271
3253 return b''.join(bytes)
3272 return b''.join(bytes)
3254
3273
3255
3274
3256 def uvarintdecodestream(fh):
3275 def uvarintdecodestream(fh):
3257 """Decode an unsigned variable length integer from a stream.
3276 """Decode an unsigned variable length integer from a stream.
3258
3277
3259 The passed argument is anything that has a ``.read(N)`` method.
3278 The passed argument is anything that has a ``.read(N)`` method.
3260
3279
3261 >>> from io import BytesIO
3280 >>> from io import BytesIO
3262 >>> uvarintdecodestream(BytesIO(b'\\x00'))
3281 >>> uvarintdecodestream(BytesIO(b'\\x00'))
3263 0
3282 0
3264 >>> uvarintdecodestream(BytesIO(b'\\x01'))
3283 >>> uvarintdecodestream(BytesIO(b'\\x01'))
3265 1
3284 1
3266 >>> uvarintdecodestream(BytesIO(b'\\x7f'))
3285 >>> uvarintdecodestream(BytesIO(b'\\x7f'))
3267 127
3286 127
3268 >>> uvarintdecodestream(BytesIO(b'\\xb9\\n'))
3287 >>> uvarintdecodestream(BytesIO(b'\\xb9\\n'))
3269 1337
3288 1337
3270 >>> uvarintdecodestream(BytesIO(b'\\x80\\x80\\x04'))
3289 >>> uvarintdecodestream(BytesIO(b'\\x80\\x80\\x04'))
3271 65536
3290 65536
3272 >>> uvarintdecodestream(BytesIO(b'\\x80'))
3291 >>> uvarintdecodestream(BytesIO(b'\\x80'))
3273 Traceback (most recent call last):
3292 Traceback (most recent call last):
3274 ...
3293 ...
3275 Abort: stream ended unexpectedly (got 0 bytes, expected 1)
3294 Abort: stream ended unexpectedly (got 0 bytes, expected 1)
3276 """
3295 """
3277 result = 0
3296 result = 0
3278 shift = 0
3297 shift = 0
3279 while True:
3298 while True:
3280 byte = ord(readexactly(fh, 1))
3299 byte = ord(readexactly(fh, 1))
3281 result |= (byte & 0x7F) << shift
3300 result |= (byte & 0x7F) << shift
3282 if not (byte & 0x80):
3301 if not (byte & 0x80):
3283 return result
3302 return result
3284 shift += 7
3303 shift += 7
3285
3304
3286
3305
3287 # Passing the '' locale means that the locale should be set according to the
3306 # Passing the '' locale means that the locale should be set according to the
3288 # user settings (environment variables).
3307 # user settings (environment variables).
3289 # Python sometimes avoids setting the global locale settings. When interfacing
3308 # Python sometimes avoids setting the global locale settings. When interfacing
3290 # with C code (e.g. the curses module or the Subversion bindings), the global
3309 # with C code (e.g. the curses module or the Subversion bindings), the global
3291 # locale settings must be initialized correctly. Python 2 does not initialize
3310 # locale settings must be initialized correctly. Python 2 does not initialize
3292 # the global locale settings on interpreter startup. Python 3 sometimes
3311 # the global locale settings on interpreter startup. Python 3 sometimes
3293 # initializes LC_CTYPE, but not consistently at least on Windows. Therefore we
3312 # initializes LC_CTYPE, but not consistently at least on Windows. Therefore we
3294 # explicitly initialize it to get consistent behavior if it's not already
3313 # explicitly initialize it to get consistent behavior if it's not already
3295 # initialized. Since CPython commit 177d921c8c03d30daa32994362023f777624b10d,
3314 # initialized. Since CPython commit 177d921c8c03d30daa32994362023f777624b10d,
3296 # LC_CTYPE is always initialized. If we require Python 3.8+, we should re-check
3315 # LC_CTYPE is always initialized. If we require Python 3.8+, we should re-check
3297 # if we can remove this code.
3316 # if we can remove this code.
3298 @contextlib.contextmanager
3317 @contextlib.contextmanager
3299 def with_lc_ctype():
3318 def with_lc_ctype():
3300 oldloc = locale.setlocale(locale.LC_CTYPE, None)
3319 oldloc = locale.setlocale(locale.LC_CTYPE, None)
3301 if oldloc == 'C':
3320 if oldloc == 'C':
3302 try:
3321 try:
3303 try:
3322 try:
3304 locale.setlocale(locale.LC_CTYPE, '')
3323 locale.setlocale(locale.LC_CTYPE, '')
3305 except locale.Error:
3324 except locale.Error:
3306 # The likely case is that the locale from the environment
3325 # The likely case is that the locale from the environment
3307 # variables is unknown.
3326 # variables is unknown.
3308 pass
3327 pass
3309 yield
3328 yield
3310 finally:
3329 finally:
3311 locale.setlocale(locale.LC_CTYPE, oldloc)
3330 locale.setlocale(locale.LC_CTYPE, oldloc)
3312 else:
3331 else:
3313 yield
3332 yield
3314
3333
3315
3334
3316 def _estimatememory() -> Optional[int]:
3335 def _estimatememory() -> Optional[int]:
3317 """Provide an estimate for the available system memory in Bytes.
3336 """Provide an estimate for the available system memory in Bytes.
3318
3337
3319 If no estimate can be provided on the platform, returns None.
3338 If no estimate can be provided on the platform, returns None.
3320 """
3339 """
3321 if pycompat.sysplatform.startswith(b'win'):
3340 if pycompat.sysplatform.startswith(b'win'):
3322 # On Windows, use the GlobalMemoryStatusEx kernel function directly.
3341 # On Windows, use the GlobalMemoryStatusEx kernel function directly.
3323 from ctypes import c_long as DWORD, c_ulonglong as DWORDLONG
3342 from ctypes import c_long as DWORD, c_ulonglong as DWORDLONG
3324 from ctypes.wintypes import ( # pytype: disable=import-error
3343 from ctypes.wintypes import ( # pytype: disable=import-error
3325 Structure,
3344 Structure,
3326 byref,
3345 byref,
3327 sizeof,
3346 sizeof,
3328 windll,
3347 windll,
3329 )
3348 )
3330
3349
3331 class MEMORYSTATUSEX(Structure):
3350 class MEMORYSTATUSEX(Structure):
3332 _fields_ = [
3351 _fields_ = [
3333 ('dwLength', DWORD),
3352 ('dwLength', DWORD),
3334 ('dwMemoryLoad', DWORD),
3353 ('dwMemoryLoad', DWORD),
3335 ('ullTotalPhys', DWORDLONG),
3354 ('ullTotalPhys', DWORDLONG),
3336 ('ullAvailPhys', DWORDLONG),
3355 ('ullAvailPhys', DWORDLONG),
3337 ('ullTotalPageFile', DWORDLONG),
3356 ('ullTotalPageFile', DWORDLONG),
3338 ('ullAvailPageFile', DWORDLONG),
3357 ('ullAvailPageFile', DWORDLONG),
3339 ('ullTotalVirtual', DWORDLONG),
3358 ('ullTotalVirtual', DWORDLONG),
3340 ('ullAvailVirtual', DWORDLONG),
3359 ('ullAvailVirtual', DWORDLONG),
3341 ('ullExtendedVirtual', DWORDLONG),
3360 ('ullExtendedVirtual', DWORDLONG),
3342 ]
3361 ]
3343
3362
3344 x = MEMORYSTATUSEX()
3363 x = MEMORYSTATUSEX()
3345 x.dwLength = sizeof(x)
3364 x.dwLength = sizeof(x)
3346 windll.kernel32.GlobalMemoryStatusEx(byref(x))
3365 windll.kernel32.GlobalMemoryStatusEx(byref(x))
3347 return x.ullAvailPhys
3366 return x.ullAvailPhys
3348
3367
3349 # On newer Unix-like systems and Mac OSX, the sysconf interface
3368 # On newer Unix-like systems and Mac OSX, the sysconf interface
3350 # can be used. _SC_PAGE_SIZE is part of POSIX; _SC_PHYS_PAGES
3369 # can be used. _SC_PAGE_SIZE is part of POSIX; _SC_PHYS_PAGES
3351 # seems to be implemented on most systems.
3370 # seems to be implemented on most systems.
3352 try:
3371 try:
3353 pagesize = os.sysconf(os.sysconf_names['SC_PAGE_SIZE'])
3372 pagesize = os.sysconf(os.sysconf_names['SC_PAGE_SIZE'])
3354 pages = os.sysconf(os.sysconf_names['SC_PHYS_PAGES'])
3373 pages = os.sysconf(os.sysconf_names['SC_PHYS_PAGES'])
3355 return pagesize * pages
3374 return pagesize * pages
3356 except OSError: # sysconf can fail
3375 except OSError: # sysconf can fail
3357 pass
3376 pass
3358 except KeyError: # unknown parameter
3377 except KeyError: # unknown parameter
3359 pass
3378 pass
General Comments 0
You need to be logged in to leave comments. Login now