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