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