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