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