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