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