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