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