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