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