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