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