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