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