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