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