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