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