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