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