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