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