##// END OF EJS Templates
util: improve finding of hgexecutable...
Simon Heimberg -
r15076:89d9f92f default
parent child Browse files
Show More
@@ -1,1719 +1,1721 b''
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, 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 elif getattr(sys.modules['__main__'], '__file__', '').endswith('hg'):
378 _sethgexecutable(sys.modules['__main__'].__file__)
377 379 else:
378 380 exe = findexe('hg') or os.path.basename(sys.argv[0])
379 381 _sethgexecutable(exe)
380 382 return _hgexecutable
381 383
382 384 def _sethgexecutable(path):
383 385 """set location of the 'hg' executable"""
384 386 global _hgexecutable
385 387 _hgexecutable = path
386 388
387 389 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
388 390 '''enhanced shell command execution.
389 391 run with environment maybe modified, maybe in different dir.
390 392
391 393 if command fails and onerr is None, return status. if ui object,
392 394 print error message and return status, else raise onerr object as
393 395 exception.
394 396
395 397 if out is specified, it is assumed to be a file-like object that has a
396 398 write() method. stdout and stderr will be redirected to out.'''
397 399 try:
398 400 sys.stdout.flush()
399 401 except Exception:
400 402 pass
401 403 def py2shell(val):
402 404 'convert python object into string that is useful to shell'
403 405 if val is None or val is False:
404 406 return '0'
405 407 if val is True:
406 408 return '1'
407 409 return str(val)
408 410 origcmd = cmd
409 411 cmd = quotecommand(cmd)
410 412 env = dict(os.environ)
411 413 env.update((k, py2shell(v)) for k, v in environ.iteritems())
412 414 env['HG'] = hgexecutable()
413 415 if out is None or out == sys.__stdout__:
414 416 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
415 417 env=env, cwd=cwd)
416 418 else:
417 419 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
418 420 env=env, cwd=cwd, stdout=subprocess.PIPE,
419 421 stderr=subprocess.STDOUT)
420 422 for line in proc.stdout:
421 423 out.write(line)
422 424 proc.wait()
423 425 rc = proc.returncode
424 426 if sys.platform == 'OpenVMS' and rc & 1:
425 427 rc = 0
426 428 if rc and onerr:
427 429 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
428 430 explainexit(rc)[0])
429 431 if errprefix:
430 432 errmsg = '%s: %s' % (errprefix, errmsg)
431 433 try:
432 434 onerr.warn(errmsg + '\n')
433 435 except AttributeError:
434 436 raise onerr(errmsg)
435 437 return rc
436 438
437 439 def checksignature(func):
438 440 '''wrap a function with code to check for calling errors'''
439 441 def check(*args, **kwargs):
440 442 try:
441 443 return func(*args, **kwargs)
442 444 except TypeError:
443 445 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
444 446 raise error.SignatureError
445 447 raise
446 448
447 449 return check
448 450
449 451 def copyfile(src, dest):
450 452 "copy a file, preserving mode and atime/mtime"
451 453 if os.path.islink(src):
452 454 try:
453 455 os.unlink(dest)
454 456 except OSError:
455 457 pass
456 458 os.symlink(os.readlink(src), dest)
457 459 else:
458 460 try:
459 461 shutil.copyfile(src, dest)
460 462 shutil.copymode(src, dest)
461 463 except shutil.Error, inst:
462 464 raise Abort(str(inst))
463 465
464 466 def copyfiles(src, dst, hardlink=None):
465 467 """Copy a directory tree using hardlinks if possible"""
466 468
467 469 if hardlink is None:
468 470 hardlink = (os.stat(src).st_dev ==
469 471 os.stat(os.path.dirname(dst)).st_dev)
470 472
471 473 num = 0
472 474 if os.path.isdir(src):
473 475 os.mkdir(dst)
474 476 for name, kind in osutil.listdir(src):
475 477 srcname = os.path.join(src, name)
476 478 dstname = os.path.join(dst, name)
477 479 hardlink, n = copyfiles(srcname, dstname, hardlink)
478 480 num += n
479 481 else:
480 482 if hardlink:
481 483 try:
482 484 oslink(src, dst)
483 485 except (IOError, OSError):
484 486 hardlink = False
485 487 shutil.copy(src, dst)
486 488 else:
487 489 shutil.copy(src, dst)
488 490 num += 1
489 491
490 492 return hardlink, num
491 493
492 494 _winreservednames = '''con prn aux nul
493 495 com1 com2 com3 com4 com5 com6 com7 com8 com9
494 496 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
495 497 _winreservedchars = ':*?"<>|'
496 498 def checkwinfilename(path):
497 499 '''Check that the base-relative path is a valid filename on Windows.
498 500 Returns None if the path is ok, or a UI string describing the problem.
499 501
500 502 >>> checkwinfilename("just/a/normal/path")
501 503 >>> checkwinfilename("foo/bar/con.xml")
502 504 "filename contains 'con', which is reserved on Windows"
503 505 >>> checkwinfilename("foo/con.xml/bar")
504 506 "filename contains 'con', which is reserved on Windows"
505 507 >>> checkwinfilename("foo/bar/xml.con")
506 508 >>> checkwinfilename("foo/bar/AUX/bla.txt")
507 509 "filename contains 'AUX', which is reserved on Windows"
508 510 >>> checkwinfilename("foo/bar/bla:.txt")
509 511 "filename contains ':', which is reserved on Windows"
510 512 >>> checkwinfilename("foo/bar/b\07la.txt")
511 513 "filename contains '\\\\x07', which is invalid on Windows"
512 514 >>> checkwinfilename("foo/bar/bla ")
513 515 "filename ends with ' ', which is not allowed on Windows"
514 516 '''
515 517 for n in path.replace('\\', '/').split('/'):
516 518 if not n:
517 519 continue
518 520 for c in n:
519 521 if c in _winreservedchars:
520 522 return _("filename contains '%s', which is reserved "
521 523 "on Windows") % c
522 524 if ord(c) <= 31:
523 525 return _("filename contains %r, which is invalid "
524 526 "on Windows") % c
525 527 base = n.split('.')[0]
526 528 if base and base.lower() in _winreservednames:
527 529 return _("filename contains '%s', which is reserved "
528 530 "on Windows") % base
529 531 t = n[-1]
530 532 if t in '. ':
531 533 return _("filename ends with '%s', which is not allowed "
532 534 "on Windows") % t
533 535
534 536 if os.name == 'nt':
535 537 checkosfilename = checkwinfilename
536 538 else:
537 539 checkosfilename = platform.checkosfilename
538 540
539 541 def makelock(info, pathname):
540 542 try:
541 543 return os.symlink(info, pathname)
542 544 except OSError, why:
543 545 if why.errno == errno.EEXIST:
544 546 raise
545 547 except AttributeError: # no symlink in os
546 548 pass
547 549
548 550 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
549 551 os.write(ld, info)
550 552 os.close(ld)
551 553
552 554 def readlock(pathname):
553 555 try:
554 556 return os.readlink(pathname)
555 557 except OSError, why:
556 558 if why.errno not in (errno.EINVAL, errno.ENOSYS):
557 559 raise
558 560 except AttributeError: # no symlink in os
559 561 pass
560 562 fp = posixfile(pathname)
561 563 r = fp.read()
562 564 fp.close()
563 565 return r
564 566
565 567 def fstat(fp):
566 568 '''stat file object that may not have fileno method.'''
567 569 try:
568 570 return os.fstat(fp.fileno())
569 571 except AttributeError:
570 572 return os.stat(fp.name)
571 573
572 574 # File system features
573 575
574 576 def checkcase(path):
575 577 """
576 578 Check whether the given path is on a case-sensitive filesystem
577 579
578 580 Requires a path (like /foo/.hg) ending with a foldable final
579 581 directory component.
580 582 """
581 583 s1 = os.stat(path)
582 584 d, b = os.path.split(path)
583 585 p2 = os.path.join(d, b.upper())
584 586 if path == p2:
585 587 p2 = os.path.join(d, b.lower())
586 588 try:
587 589 s2 = os.stat(p2)
588 590 if s2 == s1:
589 591 return False
590 592 return True
591 593 except OSError:
592 594 return True
593 595
594 596 _fspathcache = {}
595 597 def fspath(name, root):
596 598 '''Get name in the case stored in the filesystem
597 599
598 600 The name is either relative to root, or it is an absolute path starting
599 601 with root. Note that this function is unnecessary, and should not be
600 602 called, for case-sensitive filesystems (simply because it's expensive).
601 603 '''
602 604 # If name is absolute, make it relative
603 605 if name.lower().startswith(root.lower()):
604 606 l = len(root)
605 607 if name[l] == os.sep or name[l] == os.altsep:
606 608 l = l + 1
607 609 name = name[l:]
608 610
609 611 if not os.path.lexists(os.path.join(root, name)):
610 612 return None
611 613
612 614 seps = os.sep
613 615 if os.altsep:
614 616 seps = seps + os.altsep
615 617 # Protect backslashes. This gets silly very quickly.
616 618 seps.replace('\\','\\\\')
617 619 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
618 620 dir = os.path.normcase(os.path.normpath(root))
619 621 result = []
620 622 for part, sep in pattern.findall(name):
621 623 if sep:
622 624 result.append(sep)
623 625 continue
624 626
625 627 if dir not in _fspathcache:
626 628 _fspathcache[dir] = os.listdir(dir)
627 629 contents = _fspathcache[dir]
628 630
629 631 lpart = part.lower()
630 632 lenp = len(part)
631 633 for n in contents:
632 634 if lenp == len(n) and n.lower() == lpart:
633 635 result.append(n)
634 636 break
635 637 else:
636 638 # Cannot happen, as the file exists!
637 639 result.append(part)
638 640 dir = os.path.join(dir, lpart)
639 641
640 642 return ''.join(result)
641 643
642 644 def checknlink(testfile):
643 645 '''check whether hardlink count reporting works properly'''
644 646
645 647 # testfile may be open, so we need a separate file for checking to
646 648 # work around issue2543 (or testfile may get lost on Samba shares)
647 649 f1 = testfile + ".hgtmp1"
648 650 if os.path.lexists(f1):
649 651 return False
650 652 try:
651 653 posixfile(f1, 'w').close()
652 654 except IOError:
653 655 return False
654 656
655 657 f2 = testfile + ".hgtmp2"
656 658 fd = None
657 659 try:
658 660 try:
659 661 oslink(f1, f2)
660 662 except OSError:
661 663 return False
662 664
663 665 # nlinks() may behave differently for files on Windows shares if
664 666 # the file is open.
665 667 fd = posixfile(f2)
666 668 return nlinks(f2) > 1
667 669 finally:
668 670 if fd is not None:
669 671 fd.close()
670 672 for f in (f1, f2):
671 673 try:
672 674 os.unlink(f)
673 675 except OSError:
674 676 pass
675 677
676 678 return False
677 679
678 680 def endswithsep(path):
679 681 '''Check path ends with os.sep or os.altsep.'''
680 682 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
681 683
682 684 def splitpath(path):
683 685 '''Split path by os.sep.
684 686 Note that this function does not use os.altsep because this is
685 687 an alternative of simple "xxx.split(os.sep)".
686 688 It is recommended to use os.path.normpath() before using this
687 689 function if need.'''
688 690 return path.split(os.sep)
689 691
690 692 def gui():
691 693 '''Are we running in a GUI?'''
692 694 if sys.platform == 'darwin':
693 695 if 'SSH_CONNECTION' in os.environ:
694 696 # handle SSH access to a box where the user is logged in
695 697 return False
696 698 elif getattr(osutil, 'isgui', None):
697 699 # check if a CoreGraphics session is available
698 700 return osutil.isgui()
699 701 else:
700 702 # pure build; use a safe default
701 703 return True
702 704 else:
703 705 return os.name == "nt" or os.environ.get("DISPLAY")
704 706
705 707 def mktempcopy(name, emptyok=False, createmode=None):
706 708 """Create a temporary file with the same contents from name
707 709
708 710 The permission bits are copied from the original file.
709 711
710 712 If the temporary file is going to be truncated immediately, you
711 713 can use emptyok=True as an optimization.
712 714
713 715 Returns the name of the temporary file.
714 716 """
715 717 d, fn = os.path.split(name)
716 718 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
717 719 os.close(fd)
718 720 # Temporary files are created with mode 0600, which is usually not
719 721 # what we want. If the original file already exists, just copy
720 722 # its mode. Otherwise, manually obey umask.
721 723 copymode(name, temp, createmode)
722 724 if emptyok:
723 725 return temp
724 726 try:
725 727 try:
726 728 ifp = posixfile(name, "rb")
727 729 except IOError, inst:
728 730 if inst.errno == errno.ENOENT:
729 731 return temp
730 732 if not getattr(inst, 'filename', None):
731 733 inst.filename = name
732 734 raise
733 735 ofp = posixfile(temp, "wb")
734 736 for chunk in filechunkiter(ifp):
735 737 ofp.write(chunk)
736 738 ifp.close()
737 739 ofp.close()
738 740 except:
739 741 try: os.unlink(temp)
740 742 except: pass
741 743 raise
742 744 return temp
743 745
744 746 class atomictempfile(object):
745 747 '''writeable file object that atomically updates a file
746 748
747 749 All writes will go to a temporary copy of the original file. Call
748 750 close() when you are done writing, and atomictempfile will rename
749 751 the temporary copy to the original name, making the changes
750 752 visible. If the object is destroyed without being closed, all your
751 753 writes are discarded.
752 754 '''
753 755 def __init__(self, name, mode='w+b', createmode=None):
754 756 self.__name = name # permanent name
755 757 self._tempname = mktempcopy(name, emptyok=('w' in mode),
756 758 createmode=createmode)
757 759 self._fp = posixfile(self._tempname, mode)
758 760
759 761 # delegated methods
760 762 self.write = self._fp.write
761 763 self.fileno = self._fp.fileno
762 764
763 765 def close(self):
764 766 if not self._fp.closed:
765 767 self._fp.close()
766 768 rename(self._tempname, localpath(self.__name))
767 769
768 770 def discard(self):
769 771 if not self._fp.closed:
770 772 try:
771 773 os.unlink(self._tempname)
772 774 except OSError:
773 775 pass
774 776 self._fp.close()
775 777
776 778 def __del__(self):
777 779 if safehasattr(self, '_fp'): # constructor actually did something
778 780 self.discard()
779 781
780 782 def makedirs(name, mode=None):
781 783 """recursive directory creation with parent mode inheritance"""
782 784 try:
783 785 os.mkdir(name)
784 786 except OSError, err:
785 787 if err.errno == errno.EEXIST:
786 788 return
787 789 if err.errno != errno.ENOENT or not name:
788 790 raise
789 791 parent = os.path.dirname(os.path.abspath(name))
790 792 if parent == name:
791 793 raise
792 794 makedirs(parent, mode)
793 795 os.mkdir(name)
794 796 if mode is not None:
795 797 os.chmod(name, mode)
796 798
797 799 def readfile(path):
798 800 fp = open(path, 'rb')
799 801 try:
800 802 return fp.read()
801 803 finally:
802 804 fp.close()
803 805
804 806 def writefile(path, text):
805 807 fp = open(path, 'wb')
806 808 try:
807 809 fp.write(text)
808 810 finally:
809 811 fp.close()
810 812
811 813 def appendfile(path, text):
812 814 fp = open(path, 'ab')
813 815 try:
814 816 fp.write(text)
815 817 finally:
816 818 fp.close()
817 819
818 820 class chunkbuffer(object):
819 821 """Allow arbitrary sized chunks of data to be efficiently read from an
820 822 iterator over chunks of arbitrary size."""
821 823
822 824 def __init__(self, in_iter):
823 825 """in_iter is the iterator that's iterating over the input chunks.
824 826 targetsize is how big a buffer to try to maintain."""
825 827 def splitbig(chunks):
826 828 for chunk in chunks:
827 829 if len(chunk) > 2**20:
828 830 pos = 0
829 831 while pos < len(chunk):
830 832 end = pos + 2 ** 18
831 833 yield chunk[pos:end]
832 834 pos = end
833 835 else:
834 836 yield chunk
835 837 self.iter = splitbig(in_iter)
836 838 self._queue = []
837 839
838 840 def read(self, l):
839 841 """Read L bytes of data from the iterator of chunks of data.
840 842 Returns less than L bytes if the iterator runs dry."""
841 843 left = l
842 844 buf = ''
843 845 queue = self._queue
844 846 while left > 0:
845 847 # refill the queue
846 848 if not queue:
847 849 target = 2**18
848 850 for chunk in self.iter:
849 851 queue.append(chunk)
850 852 target -= len(chunk)
851 853 if target <= 0:
852 854 break
853 855 if not queue:
854 856 break
855 857
856 858 chunk = queue.pop(0)
857 859 left -= len(chunk)
858 860 if left < 0:
859 861 queue.insert(0, chunk[left:])
860 862 buf += chunk[:left]
861 863 else:
862 864 buf += chunk
863 865
864 866 return buf
865 867
866 868 def filechunkiter(f, size=65536, limit=None):
867 869 """Create a generator that produces the data in the file size
868 870 (default 65536) bytes at a time, up to optional limit (default is
869 871 to read all data). Chunks may be less than size bytes if the
870 872 chunk is the last chunk in the file, or the file is a socket or
871 873 some other type of file that sometimes reads less data than is
872 874 requested."""
873 875 assert size >= 0
874 876 assert limit is None or limit >= 0
875 877 while True:
876 878 if limit is None:
877 879 nbytes = size
878 880 else:
879 881 nbytes = min(limit, size)
880 882 s = nbytes and f.read(nbytes)
881 883 if not s:
882 884 break
883 885 if limit:
884 886 limit -= len(s)
885 887 yield s
886 888
887 889 def makedate():
888 890 lt = time.localtime()
889 891 if lt[8] == 1 and time.daylight:
890 892 tz = time.altzone
891 893 else:
892 894 tz = time.timezone
893 895 t = time.mktime(lt)
894 896 if t < 0:
895 897 hint = _("check your clock")
896 898 raise Abort(_("negative timestamp: %d") % t, hint=hint)
897 899 return t, tz
898 900
899 901 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
900 902 """represent a (unixtime, offset) tuple as a localized time.
901 903 unixtime is seconds since the epoch, and offset is the time zone's
902 904 number of seconds away from UTC. if timezone is false, do not
903 905 append time zone to string."""
904 906 t, tz = date or makedate()
905 907 if t < 0:
906 908 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
907 909 tz = 0
908 910 if "%1" in format or "%2" in format:
909 911 sign = (tz > 0) and "-" or "+"
910 912 minutes = abs(tz) // 60
911 913 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
912 914 format = format.replace("%2", "%02d" % (minutes % 60))
913 915 s = time.strftime(format, time.gmtime(float(t) - tz))
914 916 return s
915 917
916 918 def shortdate(date=None):
917 919 """turn (timestamp, tzoff) tuple into iso 8631 date."""
918 920 return datestr(date, format='%Y-%m-%d')
919 921
920 922 def strdate(string, format, defaults=[]):
921 923 """parse a localized time string and return a (unixtime, offset) tuple.
922 924 if the string cannot be parsed, ValueError is raised."""
923 925 def timezone(string):
924 926 tz = string.split()[-1]
925 927 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
926 928 sign = (tz[0] == "+") and 1 or -1
927 929 hours = int(tz[1:3])
928 930 minutes = int(tz[3:5])
929 931 return -sign * (hours * 60 + minutes) * 60
930 932 if tz == "GMT" or tz == "UTC":
931 933 return 0
932 934 return None
933 935
934 936 # NOTE: unixtime = localunixtime + offset
935 937 offset, date = timezone(string), string
936 938 if offset is not None:
937 939 date = " ".join(string.split()[:-1])
938 940
939 941 # add missing elements from defaults
940 942 usenow = False # default to using biased defaults
941 943 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
942 944 found = [True for p in part if ("%"+p) in format]
943 945 if not found:
944 946 date += "@" + defaults[part][usenow]
945 947 format += "@%" + part[0]
946 948 else:
947 949 # We've found a specific time element, less specific time
948 950 # elements are relative to today
949 951 usenow = True
950 952
951 953 timetuple = time.strptime(date, format)
952 954 localunixtime = int(calendar.timegm(timetuple))
953 955 if offset is None:
954 956 # local timezone
955 957 unixtime = int(time.mktime(timetuple))
956 958 offset = unixtime - localunixtime
957 959 else:
958 960 unixtime = localunixtime + offset
959 961 return unixtime, offset
960 962
961 963 def parsedate(date, formats=None, bias={}):
962 964 """parse a localized date/time and return a (unixtime, offset) tuple.
963 965
964 966 The date may be a "unixtime offset" string or in one of the specified
965 967 formats. If the date already is a (unixtime, offset) tuple, it is returned.
966 968 """
967 969 if not date:
968 970 return 0, 0
969 971 if isinstance(date, tuple) and len(date) == 2:
970 972 return date
971 973 if not formats:
972 974 formats = defaultdateformats
973 975 date = date.strip()
974 976 try:
975 977 when, offset = map(int, date.split(' '))
976 978 except ValueError:
977 979 # fill out defaults
978 980 now = makedate()
979 981 defaults = {}
980 982 for part in ("d", "mb", "yY", "HI", "M", "S"):
981 983 # this piece is for rounding the specific end of unknowns
982 984 b = bias.get(part)
983 985 if b is None:
984 986 if part[0] in "HMS":
985 987 b = "00"
986 988 else:
987 989 b = "0"
988 990
989 991 # this piece is for matching the generic end to today's date
990 992 n = datestr(now, "%" + part[0])
991 993
992 994 defaults[part] = (b, n)
993 995
994 996 for format in formats:
995 997 try:
996 998 when, offset = strdate(date, format, defaults)
997 999 except (ValueError, OverflowError):
998 1000 pass
999 1001 else:
1000 1002 break
1001 1003 else:
1002 1004 raise Abort(_('invalid date: %r') % date)
1003 1005 # validate explicit (probably user-specified) date and
1004 1006 # time zone offset. values must fit in signed 32 bits for
1005 1007 # current 32-bit linux runtimes. timezones go from UTC-12
1006 1008 # to UTC+14
1007 1009 if abs(when) > 0x7fffffff:
1008 1010 raise Abort(_('date exceeds 32 bits: %d') % when)
1009 1011 if when < 0:
1010 1012 raise Abort(_('negative date value: %d') % when)
1011 1013 if offset < -50400 or offset > 43200:
1012 1014 raise Abort(_('impossible time zone offset: %d') % offset)
1013 1015 return when, offset
1014 1016
1015 1017 def matchdate(date):
1016 1018 """Return a function that matches a given date match specifier
1017 1019
1018 1020 Formats include:
1019 1021
1020 1022 '{date}' match a given date to the accuracy provided
1021 1023
1022 1024 '<{date}' on or before a given date
1023 1025
1024 1026 '>{date}' on or after a given date
1025 1027
1026 1028 >>> p1 = parsedate("10:29:59")
1027 1029 >>> p2 = parsedate("10:30:00")
1028 1030 >>> p3 = parsedate("10:30:59")
1029 1031 >>> p4 = parsedate("10:31:00")
1030 1032 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1031 1033 >>> f = matchdate("10:30")
1032 1034 >>> f(p1[0])
1033 1035 False
1034 1036 >>> f(p2[0])
1035 1037 True
1036 1038 >>> f(p3[0])
1037 1039 True
1038 1040 >>> f(p4[0])
1039 1041 False
1040 1042 >>> f(p5[0])
1041 1043 False
1042 1044 """
1043 1045
1044 1046 def lower(date):
1045 1047 d = dict(mb="1", d="1")
1046 1048 return parsedate(date, extendeddateformats, d)[0]
1047 1049
1048 1050 def upper(date):
1049 1051 d = dict(mb="12", HI="23", M="59", S="59")
1050 1052 for days in ("31", "30", "29"):
1051 1053 try:
1052 1054 d["d"] = days
1053 1055 return parsedate(date, extendeddateformats, d)[0]
1054 1056 except:
1055 1057 pass
1056 1058 d["d"] = "28"
1057 1059 return parsedate(date, extendeddateformats, d)[0]
1058 1060
1059 1061 date = date.strip()
1060 1062
1061 1063 if not date:
1062 1064 raise Abort(_("dates cannot consist entirely of whitespace"))
1063 1065 elif date[0] == "<":
1064 1066 if not date[1:]:
1065 1067 raise Abort(_("invalid day spec, use '<DATE'"))
1066 1068 when = upper(date[1:])
1067 1069 return lambda x: x <= when
1068 1070 elif date[0] == ">":
1069 1071 if not date[1:]:
1070 1072 raise Abort(_("invalid day spec, use '>DATE'"))
1071 1073 when = lower(date[1:])
1072 1074 return lambda x: x >= when
1073 1075 elif date[0] == "-":
1074 1076 try:
1075 1077 days = int(date[1:])
1076 1078 except ValueError:
1077 1079 raise Abort(_("invalid day spec: %s") % date[1:])
1078 1080 if days < 0:
1079 1081 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1080 1082 % date[1:])
1081 1083 when = makedate()[0] - days * 3600 * 24
1082 1084 return lambda x: x >= when
1083 1085 elif " to " in date:
1084 1086 a, b = date.split(" to ")
1085 1087 start, stop = lower(a), upper(b)
1086 1088 return lambda x: x >= start and x <= stop
1087 1089 else:
1088 1090 start, stop = lower(date), upper(date)
1089 1091 return lambda x: x >= start and x <= stop
1090 1092
1091 1093 def shortuser(user):
1092 1094 """Return a short representation of a user name or email address."""
1093 1095 f = user.find('@')
1094 1096 if f >= 0:
1095 1097 user = user[:f]
1096 1098 f = user.find('<')
1097 1099 if f >= 0:
1098 1100 user = user[f + 1:]
1099 1101 f = user.find(' ')
1100 1102 if f >= 0:
1101 1103 user = user[:f]
1102 1104 f = user.find('.')
1103 1105 if f >= 0:
1104 1106 user = user[:f]
1105 1107 return user
1106 1108
1107 1109 def email(author):
1108 1110 '''get email of author.'''
1109 1111 r = author.find('>')
1110 1112 if r == -1:
1111 1113 r = None
1112 1114 return author[author.find('<') + 1:r]
1113 1115
1114 1116 def _ellipsis(text, maxlength):
1115 1117 if len(text) <= maxlength:
1116 1118 return text, False
1117 1119 else:
1118 1120 return "%s..." % (text[:maxlength - 3]), True
1119 1121
1120 1122 def ellipsis(text, maxlength=400):
1121 1123 """Trim string to at most maxlength (default: 400) characters."""
1122 1124 try:
1123 1125 # use unicode not to split at intermediate multi-byte sequence
1124 1126 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1125 1127 maxlength)
1126 1128 if not truncated:
1127 1129 return text
1128 1130 return utext.encode(encoding.encoding)
1129 1131 except (UnicodeDecodeError, UnicodeEncodeError):
1130 1132 return _ellipsis(text, maxlength)[0]
1131 1133
1132 1134 def bytecount(nbytes):
1133 1135 '''return byte count formatted as readable string, with units'''
1134 1136
1135 1137 units = (
1136 1138 (100, 1 << 30, _('%.0f GB')),
1137 1139 (10, 1 << 30, _('%.1f GB')),
1138 1140 (1, 1 << 30, _('%.2f GB')),
1139 1141 (100, 1 << 20, _('%.0f MB')),
1140 1142 (10, 1 << 20, _('%.1f MB')),
1141 1143 (1, 1 << 20, _('%.2f MB')),
1142 1144 (100, 1 << 10, _('%.0f KB')),
1143 1145 (10, 1 << 10, _('%.1f KB')),
1144 1146 (1, 1 << 10, _('%.2f KB')),
1145 1147 (1, 1, _('%.0f bytes')),
1146 1148 )
1147 1149
1148 1150 for multiplier, divisor, format in units:
1149 1151 if nbytes >= divisor * multiplier:
1150 1152 return format % (nbytes / float(divisor))
1151 1153 return units[-1][2] % nbytes
1152 1154
1153 1155 def uirepr(s):
1154 1156 # Avoid double backslash in Windows path repr()
1155 1157 return repr(s).replace('\\\\', '\\')
1156 1158
1157 1159 # delay import of textwrap
1158 1160 def MBTextWrapper(**kwargs):
1159 1161 class tw(textwrap.TextWrapper):
1160 1162 """
1161 1163 Extend TextWrapper for width-awareness.
1162 1164
1163 1165 Neither number of 'bytes' in any encoding nor 'characters' is
1164 1166 appropriate to calculate terminal columns for specified string.
1165 1167
1166 1168 Original TextWrapper implementation uses built-in 'len()' directly,
1167 1169 so overriding is needed to use width information of each characters.
1168 1170
1169 1171 In addition, characters classified into 'ambiguous' width are
1170 1172 treated as wide in east asian area, but as narrow in other.
1171 1173
1172 1174 This requires use decision to determine width of such characters.
1173 1175 """
1174 1176 def __init__(self, **kwargs):
1175 1177 textwrap.TextWrapper.__init__(self, **kwargs)
1176 1178
1177 1179 # for compatibility between 2.4 and 2.6
1178 1180 if getattr(self, 'drop_whitespace', None) is None:
1179 1181 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1180 1182
1181 1183 def _cutdown(self, ucstr, space_left):
1182 1184 l = 0
1183 1185 colwidth = encoding.ucolwidth
1184 1186 for i in xrange(len(ucstr)):
1185 1187 l += colwidth(ucstr[i])
1186 1188 if space_left < l:
1187 1189 return (ucstr[:i], ucstr[i:])
1188 1190 return ucstr, ''
1189 1191
1190 1192 # overriding of base class
1191 1193 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1192 1194 space_left = max(width - cur_len, 1)
1193 1195
1194 1196 if self.break_long_words:
1195 1197 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1196 1198 cur_line.append(cut)
1197 1199 reversed_chunks[-1] = res
1198 1200 elif not cur_line:
1199 1201 cur_line.append(reversed_chunks.pop())
1200 1202
1201 1203 # this overriding code is imported from TextWrapper of python 2.6
1202 1204 # to calculate columns of string by 'encoding.ucolwidth()'
1203 1205 def _wrap_chunks(self, chunks):
1204 1206 colwidth = encoding.ucolwidth
1205 1207
1206 1208 lines = []
1207 1209 if self.width <= 0:
1208 1210 raise ValueError("invalid width %r (must be > 0)" % self.width)
1209 1211
1210 1212 # Arrange in reverse order so items can be efficiently popped
1211 1213 # from a stack of chucks.
1212 1214 chunks.reverse()
1213 1215
1214 1216 while chunks:
1215 1217
1216 1218 # Start the list of chunks that will make up the current line.
1217 1219 # cur_len is just the length of all the chunks in cur_line.
1218 1220 cur_line = []
1219 1221 cur_len = 0
1220 1222
1221 1223 # Figure out which static string will prefix this line.
1222 1224 if lines:
1223 1225 indent = self.subsequent_indent
1224 1226 else:
1225 1227 indent = self.initial_indent
1226 1228
1227 1229 # Maximum width for this line.
1228 1230 width = self.width - len(indent)
1229 1231
1230 1232 # First chunk on line is whitespace -- drop it, unless this
1231 1233 # is the very beginning of the text (ie. no lines started yet).
1232 1234 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1233 1235 del chunks[-1]
1234 1236
1235 1237 while chunks:
1236 1238 l = colwidth(chunks[-1])
1237 1239
1238 1240 # Can at least squeeze this chunk onto the current line.
1239 1241 if cur_len + l <= width:
1240 1242 cur_line.append(chunks.pop())
1241 1243 cur_len += l
1242 1244
1243 1245 # Nope, this line is full.
1244 1246 else:
1245 1247 break
1246 1248
1247 1249 # The current line is full, and the next chunk is too big to
1248 1250 # fit on *any* line (not just this one).
1249 1251 if chunks and colwidth(chunks[-1]) > width:
1250 1252 self._handle_long_word(chunks, cur_line, cur_len, width)
1251 1253
1252 1254 # If the last chunk on this line is all whitespace, drop it.
1253 1255 if (self.drop_whitespace and
1254 1256 cur_line and cur_line[-1].strip() == ''):
1255 1257 del cur_line[-1]
1256 1258
1257 1259 # Convert current line back to a string and store it in list
1258 1260 # of all lines (return value).
1259 1261 if cur_line:
1260 1262 lines.append(indent + ''.join(cur_line))
1261 1263
1262 1264 return lines
1263 1265
1264 1266 global MBTextWrapper
1265 1267 MBTextWrapper = tw
1266 1268 return tw(**kwargs)
1267 1269
1268 1270 def wrap(line, width, initindent='', hangindent=''):
1269 1271 maxindent = max(len(hangindent), len(initindent))
1270 1272 if width <= maxindent:
1271 1273 # adjust for weird terminal size
1272 1274 width = max(78, maxindent + 1)
1273 1275 line = line.decode(encoding.encoding, encoding.encodingmode)
1274 1276 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1275 1277 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1276 1278 wrapper = MBTextWrapper(width=width,
1277 1279 initial_indent=initindent,
1278 1280 subsequent_indent=hangindent)
1279 1281 return wrapper.fill(line).encode(encoding.encoding)
1280 1282
1281 1283 def iterlines(iterator):
1282 1284 for chunk in iterator:
1283 1285 for line in chunk.splitlines():
1284 1286 yield line
1285 1287
1286 1288 def expandpath(path):
1287 1289 return os.path.expanduser(os.path.expandvars(path))
1288 1290
1289 1291 def hgcmd():
1290 1292 """Return the command used to execute current hg
1291 1293
1292 1294 This is different from hgexecutable() because on Windows we want
1293 1295 to avoid things opening new shell windows like batch files, so we
1294 1296 get either the python call or current executable.
1295 1297 """
1296 1298 if mainfrozen():
1297 1299 return [sys.executable]
1298 1300 return gethgcmd()
1299 1301
1300 1302 def rundetached(args, condfn):
1301 1303 """Execute the argument list in a detached process.
1302 1304
1303 1305 condfn is a callable which is called repeatedly and should return
1304 1306 True once the child process is known to have started successfully.
1305 1307 At this point, the child process PID is returned. If the child
1306 1308 process fails to start or finishes before condfn() evaluates to
1307 1309 True, return -1.
1308 1310 """
1309 1311 # Windows case is easier because the child process is either
1310 1312 # successfully starting and validating the condition or exiting
1311 1313 # on failure. We just poll on its PID. On Unix, if the child
1312 1314 # process fails to start, it will be left in a zombie state until
1313 1315 # the parent wait on it, which we cannot do since we expect a long
1314 1316 # running process on success. Instead we listen for SIGCHLD telling
1315 1317 # us our child process terminated.
1316 1318 terminated = set()
1317 1319 def handler(signum, frame):
1318 1320 terminated.add(os.wait())
1319 1321 prevhandler = None
1320 1322 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1321 1323 if SIGCHLD is not None:
1322 1324 prevhandler = signal.signal(SIGCHLD, handler)
1323 1325 try:
1324 1326 pid = spawndetached(args)
1325 1327 while not condfn():
1326 1328 if ((pid in terminated or not testpid(pid))
1327 1329 and not condfn()):
1328 1330 return -1
1329 1331 time.sleep(0.1)
1330 1332 return pid
1331 1333 finally:
1332 1334 if prevhandler is not None:
1333 1335 signal.signal(signal.SIGCHLD, prevhandler)
1334 1336
1335 1337 try:
1336 1338 any, all = any, all
1337 1339 except NameError:
1338 1340 def any(iterable):
1339 1341 for i in iterable:
1340 1342 if i:
1341 1343 return True
1342 1344 return False
1343 1345
1344 1346 def all(iterable):
1345 1347 for i in iterable:
1346 1348 if not i:
1347 1349 return False
1348 1350 return True
1349 1351
1350 1352 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1351 1353 """Return the result of interpolating items in the mapping into string s.
1352 1354
1353 1355 prefix is a single character string, or a two character string with
1354 1356 a backslash as the first character if the prefix needs to be escaped in
1355 1357 a regular expression.
1356 1358
1357 1359 fn is an optional function that will be applied to the replacement text
1358 1360 just before replacement.
1359 1361
1360 1362 escape_prefix is an optional flag that allows using doubled prefix for
1361 1363 its escaping.
1362 1364 """
1363 1365 fn = fn or (lambda s: s)
1364 1366 patterns = '|'.join(mapping.keys())
1365 1367 if escape_prefix:
1366 1368 patterns += '|' + prefix
1367 1369 if len(prefix) > 1:
1368 1370 prefix_char = prefix[1:]
1369 1371 else:
1370 1372 prefix_char = prefix
1371 1373 mapping[prefix_char] = prefix_char
1372 1374 r = re.compile(r'%s(%s)' % (prefix, patterns))
1373 1375 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1374 1376
1375 1377 def getport(port):
1376 1378 """Return the port for a given network service.
1377 1379
1378 1380 If port is an integer, it's returned as is. If it's a string, it's
1379 1381 looked up using socket.getservbyname(). If there's no matching
1380 1382 service, util.Abort is raised.
1381 1383 """
1382 1384 try:
1383 1385 return int(port)
1384 1386 except ValueError:
1385 1387 pass
1386 1388
1387 1389 try:
1388 1390 return socket.getservbyname(port)
1389 1391 except socket.error:
1390 1392 raise Abort(_("no port number associated with service '%s'") % port)
1391 1393
1392 1394 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1393 1395 '0': False, 'no': False, 'false': False, 'off': False,
1394 1396 'never': False}
1395 1397
1396 1398 def parsebool(s):
1397 1399 """Parse s into a boolean.
1398 1400
1399 1401 If s is not a valid boolean, returns None.
1400 1402 """
1401 1403 return _booleans.get(s.lower(), None)
1402 1404
1403 1405 _hexdig = '0123456789ABCDEFabcdef'
1404 1406 _hextochr = dict((a + b, chr(int(a + b, 16)))
1405 1407 for a in _hexdig for b in _hexdig)
1406 1408
1407 1409 def _urlunquote(s):
1408 1410 """unquote('abc%20def') -> 'abc def'."""
1409 1411 res = s.split('%')
1410 1412 # fastpath
1411 1413 if len(res) == 1:
1412 1414 return s
1413 1415 s = res[0]
1414 1416 for item in res[1:]:
1415 1417 try:
1416 1418 s += _hextochr[item[:2]] + item[2:]
1417 1419 except KeyError:
1418 1420 s += '%' + item
1419 1421 except UnicodeDecodeError:
1420 1422 s += unichr(int(item[:2], 16)) + item[2:]
1421 1423 return s
1422 1424
1423 1425 class url(object):
1424 1426 r"""Reliable URL parser.
1425 1427
1426 1428 This parses URLs and provides attributes for the following
1427 1429 components:
1428 1430
1429 1431 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1430 1432
1431 1433 Missing components are set to None. The only exception is
1432 1434 fragment, which is set to '' if present but empty.
1433 1435
1434 1436 If parsefragment is False, fragment is included in query. If
1435 1437 parsequery is False, query is included in path. If both are
1436 1438 False, both fragment and query are included in path.
1437 1439
1438 1440 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1439 1441
1440 1442 Note that for backward compatibility reasons, bundle URLs do not
1441 1443 take host names. That means 'bundle://../' has a path of '../'.
1442 1444
1443 1445 Examples:
1444 1446
1445 1447 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1446 1448 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1447 1449 >>> url('ssh://[::1]:2200//home/joe/repo')
1448 1450 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1449 1451 >>> url('file:///home/joe/repo')
1450 1452 <url scheme: 'file', path: '/home/joe/repo'>
1451 1453 >>> url('file:///c:/temp/foo/')
1452 1454 <url scheme: 'file', path: 'c:/temp/foo/'>
1453 1455 >>> url('bundle:foo')
1454 1456 <url scheme: 'bundle', path: 'foo'>
1455 1457 >>> url('bundle://../foo')
1456 1458 <url scheme: 'bundle', path: '../foo'>
1457 1459 >>> url(r'c:\foo\bar')
1458 1460 <url path: 'c:\\foo\\bar'>
1459 1461 >>> url(r'\\blah\blah\blah')
1460 1462 <url path: '\\\\blah\\blah\\blah'>
1461 1463
1462 1464 Authentication credentials:
1463 1465
1464 1466 >>> url('ssh://joe:xyz@x/repo')
1465 1467 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1466 1468 >>> url('ssh://joe@x/repo')
1467 1469 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1468 1470
1469 1471 Query strings and fragments:
1470 1472
1471 1473 >>> url('http://host/a?b#c')
1472 1474 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1473 1475 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1474 1476 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1475 1477 """
1476 1478
1477 1479 _safechars = "!~*'()+"
1478 1480 _safepchars = "/!~*'()+"
1479 1481 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1480 1482
1481 1483 def __init__(self, path, parsequery=True, parsefragment=True):
1482 1484 # We slowly chomp away at path until we have only the path left
1483 1485 self.scheme = self.user = self.passwd = self.host = None
1484 1486 self.port = self.path = self.query = self.fragment = None
1485 1487 self._localpath = True
1486 1488 self._hostport = ''
1487 1489 self._origpath = path
1488 1490
1489 1491 # special case for Windows drive letters and UNC paths
1490 1492 if hasdriveletter(path) or path.startswith(r'\\'):
1491 1493 self.path = path
1492 1494 return
1493 1495
1494 1496 # For compatibility reasons, we can't handle bundle paths as
1495 1497 # normal URLS
1496 1498 if path.startswith('bundle:'):
1497 1499 self.scheme = 'bundle'
1498 1500 path = path[7:]
1499 1501 if path.startswith('//'):
1500 1502 path = path[2:]
1501 1503 self.path = path
1502 1504 return
1503 1505
1504 1506 if self._matchscheme(path):
1505 1507 parts = path.split(':', 1)
1506 1508 if parts[0]:
1507 1509 self.scheme, path = parts
1508 1510 self._localpath = False
1509 1511
1510 1512 if not path:
1511 1513 path = None
1512 1514 if self._localpath:
1513 1515 self.path = ''
1514 1516 return
1515 1517 else:
1516 1518 if parsefragment and '#' in path:
1517 1519 path, self.fragment = path.split('#', 1)
1518 1520 if not path:
1519 1521 path = None
1520 1522 if self._localpath:
1521 1523 self.path = path
1522 1524 return
1523 1525
1524 1526 if parsequery and '?' in path:
1525 1527 path, self.query = path.split('?', 1)
1526 1528 if not path:
1527 1529 path = None
1528 1530 if not self.query:
1529 1531 self.query = None
1530 1532
1531 1533 # // is required to specify a host/authority
1532 1534 if path and path.startswith('//'):
1533 1535 parts = path[2:].split('/', 1)
1534 1536 if len(parts) > 1:
1535 1537 self.host, path = parts
1536 1538 path = path
1537 1539 else:
1538 1540 self.host = parts[0]
1539 1541 path = None
1540 1542 if not self.host:
1541 1543 self.host = None
1542 1544 # path of file:///d is /d
1543 1545 # path of file:///d:/ is d:/, not /d:/
1544 1546 if path and not hasdriveletter(path):
1545 1547 path = '/' + path
1546 1548
1547 1549 if self.host and '@' in self.host:
1548 1550 self.user, self.host = self.host.rsplit('@', 1)
1549 1551 if ':' in self.user:
1550 1552 self.user, self.passwd = self.user.split(':', 1)
1551 1553 if not self.host:
1552 1554 self.host = None
1553 1555
1554 1556 # Don't split on colons in IPv6 addresses without ports
1555 1557 if (self.host and ':' in self.host and
1556 1558 not (self.host.startswith('[') and self.host.endswith(']'))):
1557 1559 self._hostport = self.host
1558 1560 self.host, self.port = self.host.rsplit(':', 1)
1559 1561 if not self.host:
1560 1562 self.host = None
1561 1563
1562 1564 if (self.host and self.scheme == 'file' and
1563 1565 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1564 1566 raise Abort(_('file:// URLs can only refer to localhost'))
1565 1567
1566 1568 self.path = path
1567 1569
1568 1570 # leave the query string escaped
1569 1571 for a in ('user', 'passwd', 'host', 'port',
1570 1572 'path', 'fragment'):
1571 1573 v = getattr(self, a)
1572 1574 if v is not None:
1573 1575 setattr(self, a, _urlunquote(v))
1574 1576
1575 1577 def __repr__(self):
1576 1578 attrs = []
1577 1579 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1578 1580 'query', 'fragment'):
1579 1581 v = getattr(self, a)
1580 1582 if v is not None:
1581 1583 attrs.append('%s: %r' % (a, v))
1582 1584 return '<url %s>' % ', '.join(attrs)
1583 1585
1584 1586 def __str__(self):
1585 1587 r"""Join the URL's components back into a URL string.
1586 1588
1587 1589 Examples:
1588 1590
1589 1591 >>> str(url('http://user:pw@host:80/?foo#bar'))
1590 1592 'http://user:pw@host:80/?foo#bar'
1591 1593 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1592 1594 'http://user:pw@host:80/?foo=bar&baz=42'
1593 1595 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1594 1596 'http://user:pw@host:80/?foo=bar%3dbaz'
1595 1597 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1596 1598 'ssh://user:pw@[::1]:2200//home/joe#'
1597 1599 >>> str(url('http://localhost:80//'))
1598 1600 'http://localhost:80//'
1599 1601 >>> str(url('http://localhost:80/'))
1600 1602 'http://localhost:80/'
1601 1603 >>> str(url('http://localhost:80'))
1602 1604 'http://localhost:80/'
1603 1605 >>> str(url('bundle:foo'))
1604 1606 'bundle:foo'
1605 1607 >>> str(url('bundle://../foo'))
1606 1608 'bundle:../foo'
1607 1609 >>> str(url('path'))
1608 1610 'path'
1609 1611 >>> str(url('file:///tmp/foo/bar'))
1610 1612 'file:///tmp/foo/bar'
1611 1613 >>> print url(r'bundle:foo\bar')
1612 1614 bundle:foo\bar
1613 1615 """
1614 1616 if self._localpath:
1615 1617 s = self.path
1616 1618 if self.scheme == 'bundle':
1617 1619 s = 'bundle:' + s
1618 1620 if self.fragment:
1619 1621 s += '#' + self.fragment
1620 1622 return s
1621 1623
1622 1624 s = self.scheme + ':'
1623 1625 if self.user or self.passwd or self.host:
1624 1626 s += '//'
1625 1627 elif self.scheme and (not self.path or self.path.startswith('/')):
1626 1628 s += '//'
1627 1629 if self.user:
1628 1630 s += urllib.quote(self.user, safe=self._safechars)
1629 1631 if self.passwd:
1630 1632 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1631 1633 if self.user or self.passwd:
1632 1634 s += '@'
1633 1635 if self.host:
1634 1636 if not (self.host.startswith('[') and self.host.endswith(']')):
1635 1637 s += urllib.quote(self.host)
1636 1638 else:
1637 1639 s += self.host
1638 1640 if self.port:
1639 1641 s += ':' + urllib.quote(self.port)
1640 1642 if self.host:
1641 1643 s += '/'
1642 1644 if self.path:
1643 1645 # TODO: similar to the query string, we should not unescape the
1644 1646 # path when we store it, the path might contain '%2f' = '/',
1645 1647 # which we should *not* escape.
1646 1648 s += urllib.quote(self.path, safe=self._safepchars)
1647 1649 if self.query:
1648 1650 # we store the query in escaped form.
1649 1651 s += '?' + self.query
1650 1652 if self.fragment is not None:
1651 1653 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1652 1654 return s
1653 1655
1654 1656 def authinfo(self):
1655 1657 user, passwd = self.user, self.passwd
1656 1658 try:
1657 1659 self.user, self.passwd = None, None
1658 1660 s = str(self)
1659 1661 finally:
1660 1662 self.user, self.passwd = user, passwd
1661 1663 if not self.user:
1662 1664 return (s, None)
1663 1665 # authinfo[1] is passed to urllib2 password manager, and its
1664 1666 # URIs must not contain credentials. The host is passed in the
1665 1667 # URIs list because Python < 2.4.3 uses only that to search for
1666 1668 # a password.
1667 1669 return (s, (None, (s, self.host),
1668 1670 self.user, self.passwd or ''))
1669 1671
1670 1672 def isabs(self):
1671 1673 if self.scheme and self.scheme != 'file':
1672 1674 return True # remote URL
1673 1675 if hasdriveletter(self.path):
1674 1676 return True # absolute for our purposes - can't be joined()
1675 1677 if self.path.startswith(r'\\'):
1676 1678 return True # Windows UNC path
1677 1679 if self.path.startswith('/'):
1678 1680 return True # POSIX-style
1679 1681 return False
1680 1682
1681 1683 def localpath(self):
1682 1684 if self.scheme == 'file' or self.scheme == 'bundle':
1683 1685 path = self.path or '/'
1684 1686 # For Windows, we need to promote hosts containing drive
1685 1687 # letters to paths with drive letters.
1686 1688 if hasdriveletter(self._hostport):
1687 1689 path = self._hostport + '/' + self.path
1688 1690 elif self.host is not None and self.path:
1689 1691 path = '/' + path
1690 1692 return path
1691 1693 return self._origpath
1692 1694
1693 1695 def hasscheme(path):
1694 1696 return bool(url(path).scheme)
1695 1697
1696 1698 def hasdriveletter(path):
1697 1699 return path[1:2] == ':' and path[0:1].isalpha()
1698 1700
1699 1701 def urllocalpath(path):
1700 1702 return url(path, parsequery=False, parsefragment=False).localpath()
1701 1703
1702 1704 def hidepassword(u):
1703 1705 '''hide user credential in a url string'''
1704 1706 u = url(u)
1705 1707 if u.passwd:
1706 1708 u.passwd = '***'
1707 1709 return str(u)
1708 1710
1709 1711 def removeauth(u):
1710 1712 '''remove all authentication information from a url string'''
1711 1713 u = url(u)
1712 1714 u.user = u.passwd = None
1713 1715 return str(u)
1714 1716
1715 1717 def isatty(fd):
1716 1718 try:
1717 1719 return fd.isatty()
1718 1720 except AttributeError:
1719 1721 return False
General Comments 0
You need to be logged in to leave comments. Login now