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