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