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