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