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