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