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