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