##// END OF EJS Templates
util: fake the builtin buffer if it's missing (jython)
Ronny Pfannschmidt -
r10756:cb681cc5 default
parent child Browse files
Show More
@@ -1,1359 +1,1366
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 cStringIO, errno, re, shutil, sys, tempfile, traceback
19 19 import os, stat, time, calendar, textwrap, signal
20 20 import imp
21 21
22 22 # Python compatibility
23 23
24 24 def sha1(s):
25 25 return _fastsha1(s)
26 26
27 27 def _fastsha1(s):
28 28 # This function will import sha1 from hashlib or sha (whichever is
29 29 # available) and overwrite itself with it on the first call.
30 30 # Subsequent calls will go directly to the imported function.
31 31 try:
32 32 from hashlib import sha1 as _sha1
33 33 except ImportError:
34 34 from sha import sha as _sha1
35 35 global _fastsha1, sha1
36 36 _fastsha1 = sha1 = _sha1
37 37 return _sha1(s)
38 38
39 import __builtin__
40
41 def fakebuffer(sliceable, offset=0):
42 return sliceable[offset:]
43 if not hasattr(__builtin__, 'buffer'):
44 __builtin__.buffer = fakebuffer
45
39 46 import subprocess
40 47 closefds = os.name == 'posix'
41 48
42 49 def popen2(cmd, env=None, newlines=False):
43 50 # Setting bufsize to -1 lets the system decide the buffer size.
44 51 # The default for bufsize is 0, meaning unbuffered. This leads to
45 52 # poor performance on Mac OS X: http://bugs.python.org/issue4194
46 53 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
47 54 close_fds=closefds,
48 55 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
49 56 universal_newlines=newlines,
50 57 env=env)
51 58 return p.stdin, p.stdout
52 59
53 60 def popen3(cmd, env=None, newlines=False):
54 61 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
55 62 close_fds=closefds,
56 63 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
57 64 stderr=subprocess.PIPE,
58 65 universal_newlines=newlines,
59 66 env=env)
60 67 return p.stdin, p.stdout, p.stderr
61 68
62 69 def version():
63 70 """Return version information if available."""
64 71 try:
65 72 import __version__
66 73 return __version__.version
67 74 except ImportError:
68 75 return 'unknown'
69 76
70 77 # used by parsedate
71 78 defaultdateformats = (
72 79 '%Y-%m-%d %H:%M:%S',
73 80 '%Y-%m-%d %I:%M:%S%p',
74 81 '%Y-%m-%d %H:%M',
75 82 '%Y-%m-%d %I:%M%p',
76 83 '%Y-%m-%d',
77 84 '%m-%d',
78 85 '%m/%d',
79 86 '%m/%d/%y',
80 87 '%m/%d/%Y',
81 88 '%a %b %d %H:%M:%S %Y',
82 89 '%a %b %d %I:%M:%S%p %Y',
83 90 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
84 91 '%b %d %H:%M:%S %Y',
85 92 '%b %d %I:%M:%S%p %Y',
86 93 '%b %d %H:%M:%S',
87 94 '%b %d %I:%M:%S%p',
88 95 '%b %d %H:%M',
89 96 '%b %d %I:%M%p',
90 97 '%b %d %Y',
91 98 '%b %d',
92 99 '%H:%M:%S',
93 100 '%I:%M:%S%p',
94 101 '%H:%M',
95 102 '%I:%M%p',
96 103 )
97 104
98 105 extendeddateformats = defaultdateformats + (
99 106 "%Y",
100 107 "%Y-%m",
101 108 "%b",
102 109 "%b %Y",
103 110 )
104 111
105 112 def cachefunc(func):
106 113 '''cache the result of function calls'''
107 114 # XXX doesn't handle keywords args
108 115 cache = {}
109 116 if func.func_code.co_argcount == 1:
110 117 # we gain a small amount of time because
111 118 # we don't need to pack/unpack the list
112 119 def f(arg):
113 120 if arg not in cache:
114 121 cache[arg] = func(arg)
115 122 return cache[arg]
116 123 else:
117 124 def f(*args):
118 125 if args not in cache:
119 126 cache[args] = func(*args)
120 127 return cache[args]
121 128
122 129 return f
123 130
124 131 def lrucachefunc(func):
125 132 '''cache most recent results of function calls'''
126 133 cache = {}
127 134 order = []
128 135 if func.func_code.co_argcount == 1:
129 136 def f(arg):
130 137 if arg not in cache:
131 138 if len(cache) > 20:
132 139 del cache[order.pop(0)]
133 140 cache[arg] = func(arg)
134 141 else:
135 142 order.remove(arg)
136 143 order.append(arg)
137 144 return cache[arg]
138 145 else:
139 146 def f(*args):
140 147 if args not in cache:
141 148 if len(cache) > 20:
142 149 del cache[order.pop(0)]
143 150 cache[args] = func(*args)
144 151 else:
145 152 order.remove(args)
146 153 order.append(args)
147 154 return cache[args]
148 155
149 156 return f
150 157
151 158 class propertycache(object):
152 159 def __init__(self, func):
153 160 self.func = func
154 161 self.name = func.__name__
155 162 def __get__(self, obj, type=None):
156 163 result = self.func(obj)
157 164 setattr(obj, self.name, result)
158 165 return result
159 166
160 167 def pipefilter(s, cmd):
161 168 '''filter string S through command CMD, returning its output'''
162 169 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
163 170 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
164 171 pout, perr = p.communicate(s)
165 172 return pout
166 173
167 174 def tempfilter(s, cmd):
168 175 '''filter string S through a pair of temporary files with CMD.
169 176 CMD is used as a template to create the real command to be run,
170 177 with the strings INFILE and OUTFILE replaced by the real names of
171 178 the temporary files generated.'''
172 179 inname, outname = None, None
173 180 try:
174 181 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
175 182 fp = os.fdopen(infd, 'wb')
176 183 fp.write(s)
177 184 fp.close()
178 185 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
179 186 os.close(outfd)
180 187 cmd = cmd.replace('INFILE', inname)
181 188 cmd = cmd.replace('OUTFILE', outname)
182 189 code = os.system(cmd)
183 190 if sys.platform == 'OpenVMS' and code & 1:
184 191 code = 0
185 192 if code:
186 193 raise Abort(_("command '%s' failed: %s") %
187 194 (cmd, explain_exit(code)))
188 195 return open(outname, 'rb').read()
189 196 finally:
190 197 try:
191 198 if inname:
192 199 os.unlink(inname)
193 200 except:
194 201 pass
195 202 try:
196 203 if outname:
197 204 os.unlink(outname)
198 205 except:
199 206 pass
200 207
201 208 filtertable = {
202 209 'tempfile:': tempfilter,
203 210 'pipe:': pipefilter,
204 211 }
205 212
206 213 def filter(s, cmd):
207 214 "filter a string through a command that transforms its input to its output"
208 215 for name, fn in filtertable.iteritems():
209 216 if cmd.startswith(name):
210 217 return fn(s, cmd[len(name):].lstrip())
211 218 return pipefilter(s, cmd)
212 219
213 220 def binary(s):
214 221 """return true if a string is binary data"""
215 222 return bool(s and '\0' in s)
216 223
217 224 def increasingchunks(source, min=1024, max=65536):
218 225 '''return no less than min bytes per chunk while data remains,
219 226 doubling min after each chunk until it reaches max'''
220 227 def log2(x):
221 228 if not x:
222 229 return 0
223 230 i = 0
224 231 while x:
225 232 x >>= 1
226 233 i += 1
227 234 return i - 1
228 235
229 236 buf = []
230 237 blen = 0
231 238 for chunk in source:
232 239 buf.append(chunk)
233 240 blen += len(chunk)
234 241 if blen >= min:
235 242 if min < max:
236 243 min = min << 1
237 244 nmin = 1 << log2(blen)
238 245 if nmin > min:
239 246 min = nmin
240 247 if min > max:
241 248 min = max
242 249 yield ''.join(buf)
243 250 blen = 0
244 251 buf = []
245 252 if buf:
246 253 yield ''.join(buf)
247 254
248 255 Abort = error.Abort
249 256
250 257 def always(fn):
251 258 return True
252 259
253 260 def never(fn):
254 261 return False
255 262
256 263 def pathto(root, n1, n2):
257 264 '''return the relative path from one place to another.
258 265 root should use os.sep to separate directories
259 266 n1 should use os.sep to separate directories
260 267 n2 should use "/" to separate directories
261 268 returns an os.sep-separated path.
262 269
263 270 If n1 is a relative path, it's assumed it's
264 271 relative to root.
265 272 n2 should always be relative to root.
266 273 '''
267 274 if not n1:
268 275 return localpath(n2)
269 276 if os.path.isabs(n1):
270 277 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
271 278 return os.path.join(root, localpath(n2))
272 279 n2 = '/'.join((pconvert(root), n2))
273 280 a, b = splitpath(n1), n2.split('/')
274 281 a.reverse()
275 282 b.reverse()
276 283 while a and b and a[-1] == b[-1]:
277 284 a.pop()
278 285 b.pop()
279 286 b.reverse()
280 287 return os.sep.join((['..'] * len(a)) + b) or '.'
281 288
282 289 def canonpath(root, cwd, myname):
283 290 """return the canonical path of myname, given cwd and root"""
284 291 if endswithsep(root):
285 292 rootsep = root
286 293 else:
287 294 rootsep = root + os.sep
288 295 name = myname
289 296 if not os.path.isabs(name):
290 297 name = os.path.join(root, cwd, name)
291 298 name = os.path.normpath(name)
292 299 audit_path = path_auditor(root)
293 300 if name != rootsep and name.startswith(rootsep):
294 301 name = name[len(rootsep):]
295 302 audit_path(name)
296 303 return pconvert(name)
297 304 elif name == root:
298 305 return ''
299 306 else:
300 307 # Determine whether `name' is in the hierarchy at or beneath `root',
301 308 # by iterating name=dirname(name) until that causes no change (can't
302 309 # check name == '/', because that doesn't work on windows). For each
303 310 # `name', compare dev/inode numbers. If they match, the list `rel'
304 311 # holds the reversed list of components making up the relative file
305 312 # name we want.
306 313 root_st = os.stat(root)
307 314 rel = []
308 315 while True:
309 316 try:
310 317 name_st = os.stat(name)
311 318 except OSError:
312 319 break
313 320 if samestat(name_st, root_st):
314 321 if not rel:
315 322 # name was actually the same as root (maybe a symlink)
316 323 return ''
317 324 rel.reverse()
318 325 name = os.path.join(*rel)
319 326 audit_path(name)
320 327 return pconvert(name)
321 328 dirname, basename = os.path.split(name)
322 329 rel.append(basename)
323 330 if dirname == name:
324 331 break
325 332 name = dirname
326 333
327 334 raise Abort('%s not under root' % myname)
328 335
329 336 _hgexecutable = None
330 337
331 338 def main_is_frozen():
332 339 """return True if we are a frozen executable.
333 340
334 341 The code supports py2exe (most common, Windows only) and tools/freeze
335 342 (portable, not much used).
336 343 """
337 344 return (hasattr(sys, "frozen") or # new py2exe
338 345 hasattr(sys, "importers") or # old py2exe
339 346 imp.is_frozen("__main__")) # tools/freeze
340 347
341 348 def hgexecutable():
342 349 """return location of the 'hg' executable.
343 350
344 351 Defaults to $HG or 'hg' in the search path.
345 352 """
346 353 if _hgexecutable is None:
347 354 hg = os.environ.get('HG')
348 355 if hg:
349 356 set_hgexecutable(hg)
350 357 elif main_is_frozen():
351 358 set_hgexecutable(sys.executable)
352 359 else:
353 360 exe = find_exe('hg') or os.path.basename(sys.argv[0])
354 361 set_hgexecutable(exe)
355 362 return _hgexecutable
356 363
357 364 def set_hgexecutable(path):
358 365 """set location of the 'hg' executable"""
359 366 global _hgexecutable
360 367 _hgexecutable = path
361 368
362 369 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
363 370 '''enhanced shell command execution.
364 371 run with environment maybe modified, maybe in different dir.
365 372
366 373 if command fails and onerr is None, return status. if ui object,
367 374 print error message and return status, else raise onerr object as
368 375 exception.'''
369 376 def py2shell(val):
370 377 'convert python object into string that is useful to shell'
371 378 if val is None or val is False:
372 379 return '0'
373 380 if val is True:
374 381 return '1'
375 382 return str(val)
376 383 origcmd = cmd
377 384 if os.name == 'nt':
378 385 cmd = '"%s"' % cmd
379 386 env = dict(os.environ)
380 387 env.update((k, py2shell(v)) for k, v in environ.iteritems())
381 388 env['HG'] = hgexecutable()
382 389 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
383 390 env=env, cwd=cwd)
384 391 if sys.platform == 'OpenVMS' and rc & 1:
385 392 rc = 0
386 393 if rc and onerr:
387 394 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
388 395 explain_exit(rc)[0])
389 396 if errprefix:
390 397 errmsg = '%s: %s' % (errprefix, errmsg)
391 398 try:
392 399 onerr.warn(errmsg + '\n')
393 400 except AttributeError:
394 401 raise onerr(errmsg)
395 402 return rc
396 403
397 404 def checksignature(func):
398 405 '''wrap a function with code to check for calling errors'''
399 406 def check(*args, **kwargs):
400 407 try:
401 408 return func(*args, **kwargs)
402 409 except TypeError:
403 410 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
404 411 raise error.SignatureError
405 412 raise
406 413
407 414 return check
408 415
409 416 # os.path.lexists is not available on python2.3
410 417 def lexists(filename):
411 418 "test whether a file with this name exists. does not follow symlinks"
412 419 try:
413 420 os.lstat(filename)
414 421 except:
415 422 return False
416 423 return True
417 424
418 425 def unlink(f):
419 426 """unlink and remove the directory if it is empty"""
420 427 os.unlink(f)
421 428 # try removing directories that might now be empty
422 429 try:
423 430 os.removedirs(os.path.dirname(f))
424 431 except OSError:
425 432 pass
426 433
427 434 def copyfile(src, dest):
428 435 "copy a file, preserving mode and atime/mtime"
429 436 if os.path.islink(src):
430 437 try:
431 438 os.unlink(dest)
432 439 except:
433 440 pass
434 441 os.symlink(os.readlink(src), dest)
435 442 else:
436 443 try:
437 444 shutil.copyfile(src, dest)
438 445 shutil.copystat(src, dest)
439 446 except shutil.Error, inst:
440 447 raise Abort(str(inst))
441 448
442 449 def copyfiles(src, dst, hardlink=None):
443 450 """Copy a directory tree using hardlinks if possible"""
444 451
445 452 if hardlink is None:
446 453 hardlink = (os.stat(src).st_dev ==
447 454 os.stat(os.path.dirname(dst)).st_dev)
448 455
449 456 if os.path.isdir(src):
450 457 os.mkdir(dst)
451 458 for name, kind in osutil.listdir(src):
452 459 srcname = os.path.join(src, name)
453 460 dstname = os.path.join(dst, name)
454 461 copyfiles(srcname, dstname, hardlink)
455 462 else:
456 463 if hardlink:
457 464 try:
458 465 os_link(src, dst)
459 466 except (IOError, OSError):
460 467 hardlink = False
461 468 shutil.copy(src, dst)
462 469 else:
463 470 shutil.copy(src, dst)
464 471
465 472 class path_auditor(object):
466 473 '''ensure that a filesystem path contains no banned components.
467 474 the following properties of a path are checked:
468 475
469 476 - under top-level .hg
470 477 - starts at the root of a windows drive
471 478 - contains ".."
472 479 - traverses a symlink (e.g. a/symlink_here/b)
473 480 - inside a nested repository'''
474 481
475 482 def __init__(self, root):
476 483 self.audited = set()
477 484 self.auditeddir = set()
478 485 self.root = root
479 486
480 487 def __call__(self, path):
481 488 if path in self.audited:
482 489 return
483 490 normpath = os.path.normcase(path)
484 491 parts = splitpath(normpath)
485 492 if (os.path.splitdrive(path)[0]
486 493 or parts[0].lower() in ('.hg', '.hg.', '')
487 494 or os.pardir in parts):
488 495 raise Abort(_("path contains illegal component: %s") % path)
489 496 if '.hg' in path.lower():
490 497 lparts = [p.lower() for p in parts]
491 498 for p in '.hg', '.hg.':
492 499 if p in lparts[1:]:
493 500 pos = lparts.index(p)
494 501 base = os.path.join(*parts[:pos])
495 502 raise Abort(_('path %r is inside repo %r') % (path, base))
496 503 def check(prefix):
497 504 curpath = os.path.join(self.root, prefix)
498 505 try:
499 506 st = os.lstat(curpath)
500 507 except OSError, err:
501 508 # EINVAL can be raised as invalid path syntax under win32.
502 509 # They must be ignored for patterns can be checked too.
503 510 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
504 511 raise
505 512 else:
506 513 if stat.S_ISLNK(st.st_mode):
507 514 raise Abort(_('path %r traverses symbolic link %r') %
508 515 (path, prefix))
509 516 elif (stat.S_ISDIR(st.st_mode) and
510 517 os.path.isdir(os.path.join(curpath, '.hg'))):
511 518 raise Abort(_('path %r is inside repo %r') %
512 519 (path, prefix))
513 520 parts.pop()
514 521 prefixes = []
515 522 while parts:
516 523 prefix = os.sep.join(parts)
517 524 if prefix in self.auditeddir:
518 525 break
519 526 check(prefix)
520 527 prefixes.append(prefix)
521 528 parts.pop()
522 529
523 530 self.audited.add(path)
524 531 # only add prefixes to the cache after checking everything: we don't
525 532 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
526 533 self.auditeddir.update(prefixes)
527 534
528 535 def nlinks(pathname):
529 536 """Return number of hardlinks for the given file."""
530 537 return os.lstat(pathname).st_nlink
531 538
532 539 if hasattr(os, 'link'):
533 540 os_link = os.link
534 541 else:
535 542 def os_link(src, dst):
536 543 raise OSError(0, _("Hardlinks not supported"))
537 544
538 545 def lookup_reg(key, name=None, scope=None):
539 546 return None
540 547
541 548 def hidewindow():
542 549 """Hide current shell window.
543 550
544 551 Used to hide the window opened when starting asynchronous
545 552 child process under Windows, unneeded on other systems.
546 553 """
547 554 pass
548 555
549 556 if os.name == 'nt':
550 557 from windows import *
551 558 else:
552 559 from posix import *
553 560
554 561 def makelock(info, pathname):
555 562 try:
556 563 return os.symlink(info, pathname)
557 564 except OSError, why:
558 565 if why.errno == errno.EEXIST:
559 566 raise
560 567 except AttributeError: # no symlink in os
561 568 pass
562 569
563 570 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
564 571 os.write(ld, info)
565 572 os.close(ld)
566 573
567 574 def readlock(pathname):
568 575 try:
569 576 return os.readlink(pathname)
570 577 except OSError, why:
571 578 if why.errno not in (errno.EINVAL, errno.ENOSYS):
572 579 raise
573 580 except AttributeError: # no symlink in os
574 581 pass
575 582 return posixfile(pathname).read()
576 583
577 584 def fstat(fp):
578 585 '''stat file object that may not have fileno method.'''
579 586 try:
580 587 return os.fstat(fp.fileno())
581 588 except AttributeError:
582 589 return os.stat(fp.name)
583 590
584 591 # File system features
585 592
586 593 def checkcase(path):
587 594 """
588 595 Check whether the given path is on a case-sensitive filesystem
589 596
590 597 Requires a path (like /foo/.hg) ending with a foldable final
591 598 directory component.
592 599 """
593 600 s1 = os.stat(path)
594 601 d, b = os.path.split(path)
595 602 p2 = os.path.join(d, b.upper())
596 603 if path == p2:
597 604 p2 = os.path.join(d, b.lower())
598 605 try:
599 606 s2 = os.stat(p2)
600 607 if s2 == s1:
601 608 return False
602 609 return True
603 610 except:
604 611 return True
605 612
606 613 _fspathcache = {}
607 614 def fspath(name, root):
608 615 '''Get name in the case stored in the filesystem
609 616
610 617 The name is either relative to root, or it is an absolute path starting
611 618 with root. Note that this function is unnecessary, and should not be
612 619 called, for case-sensitive filesystems (simply because it's expensive).
613 620 '''
614 621 # If name is absolute, make it relative
615 622 if name.lower().startswith(root.lower()):
616 623 l = len(root)
617 624 if name[l] == os.sep or name[l] == os.altsep:
618 625 l = l + 1
619 626 name = name[l:]
620 627
621 628 if not os.path.exists(os.path.join(root, name)):
622 629 return None
623 630
624 631 seps = os.sep
625 632 if os.altsep:
626 633 seps = seps + os.altsep
627 634 # Protect backslashes. This gets silly very quickly.
628 635 seps.replace('\\','\\\\')
629 636 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
630 637 dir = os.path.normcase(os.path.normpath(root))
631 638 result = []
632 639 for part, sep in pattern.findall(name):
633 640 if sep:
634 641 result.append(sep)
635 642 continue
636 643
637 644 if dir not in _fspathcache:
638 645 _fspathcache[dir] = os.listdir(dir)
639 646 contents = _fspathcache[dir]
640 647
641 648 lpart = part.lower()
642 649 lenp = len(part)
643 650 for n in contents:
644 651 if lenp == len(n) and n.lower() == lpart:
645 652 result.append(n)
646 653 break
647 654 else:
648 655 # Cannot happen, as the file exists!
649 656 result.append(part)
650 657 dir = os.path.join(dir, lpart)
651 658
652 659 return ''.join(result)
653 660
654 661 def checkexec(path):
655 662 """
656 663 Check whether the given path is on a filesystem with UNIX-like exec flags
657 664
658 665 Requires a directory (like /foo/.hg)
659 666 """
660 667
661 668 # VFAT on some Linux versions can flip mode but it doesn't persist
662 669 # a FS remount. Frequently we can detect it if files are created
663 670 # with exec bit on.
664 671
665 672 try:
666 673 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
667 674 fh, fn = tempfile.mkstemp("", "", path)
668 675 try:
669 676 os.close(fh)
670 677 m = os.stat(fn).st_mode & 0777
671 678 new_file_has_exec = m & EXECFLAGS
672 679 os.chmod(fn, m ^ EXECFLAGS)
673 680 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
674 681 finally:
675 682 os.unlink(fn)
676 683 except (IOError, OSError):
677 684 # we don't care, the user probably won't be able to commit anyway
678 685 return False
679 686 return not (new_file_has_exec or exec_flags_cannot_flip)
680 687
681 688 def checklink(path):
682 689 """check whether the given path is on a symlink-capable filesystem"""
683 690 # mktemp is not racy because symlink creation will fail if the
684 691 # file already exists
685 692 name = tempfile.mktemp(dir=path)
686 693 try:
687 694 os.symlink(".", name)
688 695 os.unlink(name)
689 696 return True
690 697 except (OSError, AttributeError):
691 698 return False
692 699
693 700 def needbinarypatch():
694 701 """return True if patches should be applied in binary mode by default."""
695 702 return os.name == 'nt'
696 703
697 704 def endswithsep(path):
698 705 '''Check path ends with os.sep or os.altsep.'''
699 706 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
700 707
701 708 def splitpath(path):
702 709 '''Split path by os.sep.
703 710 Note that this function does not use os.altsep because this is
704 711 an alternative of simple "xxx.split(os.sep)".
705 712 It is recommended to use os.path.normpath() before using this
706 713 function if need.'''
707 714 return path.split(os.sep)
708 715
709 716 def gui():
710 717 '''Are we running in a GUI?'''
711 718 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
712 719
713 720 def mktempcopy(name, emptyok=False, createmode=None):
714 721 """Create a temporary file with the same contents from name
715 722
716 723 The permission bits are copied from the original file.
717 724
718 725 If the temporary file is going to be truncated immediately, you
719 726 can use emptyok=True as an optimization.
720 727
721 728 Returns the name of the temporary file.
722 729 """
723 730 d, fn = os.path.split(name)
724 731 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
725 732 os.close(fd)
726 733 # Temporary files are created with mode 0600, which is usually not
727 734 # what we want. If the original file already exists, just copy
728 735 # its mode. Otherwise, manually obey umask.
729 736 try:
730 737 st_mode = os.lstat(name).st_mode & 0777
731 738 except OSError, inst:
732 739 if inst.errno != errno.ENOENT:
733 740 raise
734 741 st_mode = createmode
735 742 if st_mode is None:
736 743 st_mode = ~umask
737 744 st_mode &= 0666
738 745 os.chmod(temp, st_mode)
739 746 if emptyok:
740 747 return temp
741 748 try:
742 749 try:
743 750 ifp = posixfile(name, "rb")
744 751 except IOError, inst:
745 752 if inst.errno == errno.ENOENT:
746 753 return temp
747 754 if not getattr(inst, 'filename', None):
748 755 inst.filename = name
749 756 raise
750 757 ofp = posixfile(temp, "wb")
751 758 for chunk in filechunkiter(ifp):
752 759 ofp.write(chunk)
753 760 ifp.close()
754 761 ofp.close()
755 762 except:
756 763 try: os.unlink(temp)
757 764 except: pass
758 765 raise
759 766 return temp
760 767
761 768 class atomictempfile(object):
762 769 """file-like object that atomically updates a file
763 770
764 771 All writes will be redirected to a temporary copy of the original
765 772 file. When rename is called, the copy is renamed to the original
766 773 name, making the changes visible.
767 774 """
768 775 def __init__(self, name, mode, createmode):
769 776 self.__name = name
770 777 self._fp = None
771 778 self.temp = mktempcopy(name, emptyok=('w' in mode),
772 779 createmode=createmode)
773 780 self._fp = posixfile(self.temp, mode)
774 781
775 782 def __getattr__(self, name):
776 783 return getattr(self._fp, name)
777 784
778 785 def rename(self):
779 786 if not self._fp.closed:
780 787 self._fp.close()
781 788 rename(self.temp, localpath(self.__name))
782 789
783 790 def __del__(self):
784 791 if not self._fp:
785 792 return
786 793 if not self._fp.closed:
787 794 try:
788 795 os.unlink(self.temp)
789 796 except: pass
790 797 self._fp.close()
791 798
792 799 def makedirs(name, mode=None):
793 800 """recursive directory creation with parent mode inheritance"""
794 801 try:
795 802 os.mkdir(name)
796 803 if mode is not None:
797 804 os.chmod(name, mode)
798 805 return
799 806 except OSError, err:
800 807 if err.errno == errno.EEXIST:
801 808 return
802 809 if err.errno != errno.ENOENT:
803 810 raise
804 811 parent = os.path.abspath(os.path.dirname(name))
805 812 makedirs(parent, mode)
806 813 makedirs(name, mode)
807 814
808 815 class opener(object):
809 816 """Open files relative to a base directory
810 817
811 818 This class is used to hide the details of COW semantics and
812 819 remote file access from higher level code.
813 820 """
814 821 def __init__(self, base, audit=True):
815 822 self.base = base
816 823 if audit:
817 824 self.audit_path = path_auditor(base)
818 825 else:
819 826 self.audit_path = always
820 827 self.createmode = None
821 828
822 829 @propertycache
823 830 def _can_symlink(self):
824 831 return checklink(self.base)
825 832
826 833 def _fixfilemode(self, name):
827 834 if self.createmode is None:
828 835 return
829 836 os.chmod(name, self.createmode & 0666)
830 837
831 838 def __call__(self, path, mode="r", text=False, atomictemp=False):
832 839 self.audit_path(path)
833 840 f = os.path.join(self.base, path)
834 841
835 842 if not text and "b" not in mode:
836 843 mode += "b" # for that other OS
837 844
838 845 nlink = -1
839 846 if mode not in ("r", "rb"):
840 847 try:
841 848 nlink = nlinks(f)
842 849 except OSError:
843 850 nlink = 0
844 851 d = os.path.dirname(f)
845 852 if not os.path.isdir(d):
846 853 makedirs(d, self.createmode)
847 854 if atomictemp:
848 855 return atomictempfile(f, mode, self.createmode)
849 856 if nlink > 1:
850 857 rename(mktempcopy(f), f)
851 858 fp = posixfile(f, mode)
852 859 if nlink == 0:
853 860 self._fixfilemode(f)
854 861 return fp
855 862
856 863 def symlink(self, src, dst):
857 864 self.audit_path(dst)
858 865 linkname = os.path.join(self.base, dst)
859 866 try:
860 867 os.unlink(linkname)
861 868 except OSError:
862 869 pass
863 870
864 871 dirname = os.path.dirname(linkname)
865 872 if not os.path.exists(dirname):
866 873 makedirs(dirname, self.createmode)
867 874
868 875 if self._can_symlink:
869 876 try:
870 877 os.symlink(src, linkname)
871 878 except OSError, err:
872 879 raise OSError(err.errno, _('could not symlink to %r: %s') %
873 880 (src, err.strerror), linkname)
874 881 else:
875 882 f = self(dst, "w")
876 883 f.write(src)
877 884 f.close()
878 885 self._fixfilemode(dst)
879 886
880 887 class chunkbuffer(object):
881 888 """Allow arbitrary sized chunks of data to be efficiently read from an
882 889 iterator over chunks of arbitrary size."""
883 890
884 891 def __init__(self, in_iter):
885 892 """in_iter is the iterator that's iterating over the input chunks.
886 893 targetsize is how big a buffer to try to maintain."""
887 894 self.iter = iter(in_iter)
888 895 self.buf = ''
889 896 self.targetsize = 2**16
890 897
891 898 def read(self, l):
892 899 """Read L bytes of data from the iterator of chunks of data.
893 900 Returns less than L bytes if the iterator runs dry."""
894 901 if l > len(self.buf) and self.iter:
895 902 # Clamp to a multiple of self.targetsize
896 903 targetsize = max(l, self.targetsize)
897 904 collector = cStringIO.StringIO()
898 905 collector.write(self.buf)
899 906 collected = len(self.buf)
900 907 for chunk in self.iter:
901 908 collector.write(chunk)
902 909 collected += len(chunk)
903 910 if collected >= targetsize:
904 911 break
905 912 if collected < targetsize:
906 913 self.iter = False
907 914 self.buf = collector.getvalue()
908 915 if len(self.buf) == l:
909 916 s, self.buf = str(self.buf), ''
910 917 else:
911 918 s, self.buf = self.buf[:l], buffer(self.buf, l)
912 919 return s
913 920
914 921 def filechunkiter(f, size=65536, limit=None):
915 922 """Create a generator that produces the data in the file size
916 923 (default 65536) bytes at a time, up to optional limit (default is
917 924 to read all data). Chunks may be less than size bytes if the
918 925 chunk is the last chunk in the file, or the file is a socket or
919 926 some other type of file that sometimes reads less data than is
920 927 requested."""
921 928 assert size >= 0
922 929 assert limit is None or limit >= 0
923 930 while True:
924 931 if limit is None:
925 932 nbytes = size
926 933 else:
927 934 nbytes = min(limit, size)
928 935 s = nbytes and f.read(nbytes)
929 936 if not s:
930 937 break
931 938 if limit:
932 939 limit -= len(s)
933 940 yield s
934 941
935 942 def makedate():
936 943 lt = time.localtime()
937 944 if lt[8] == 1 and time.daylight:
938 945 tz = time.altzone
939 946 else:
940 947 tz = time.timezone
941 948 return time.mktime(lt), tz
942 949
943 950 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
944 951 """represent a (unixtime, offset) tuple as a localized time.
945 952 unixtime is seconds since the epoch, and offset is the time zone's
946 953 number of seconds away from UTC. if timezone is false, do not
947 954 append time zone to string."""
948 955 t, tz = date or makedate()
949 956 if "%1" in format or "%2" in format:
950 957 sign = (tz > 0) and "-" or "+"
951 958 minutes = abs(tz) // 60
952 959 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
953 960 format = format.replace("%2", "%02d" % (minutes % 60))
954 961 s = time.strftime(format, time.gmtime(float(t) - tz))
955 962 return s
956 963
957 964 def shortdate(date=None):
958 965 """turn (timestamp, tzoff) tuple into iso 8631 date."""
959 966 return datestr(date, format='%Y-%m-%d')
960 967
961 968 def strdate(string, format, defaults=[]):
962 969 """parse a localized time string and return a (unixtime, offset) tuple.
963 970 if the string cannot be parsed, ValueError is raised."""
964 971 def timezone(string):
965 972 tz = string.split()[-1]
966 973 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
967 974 sign = (tz[0] == "+") and 1 or -1
968 975 hours = int(tz[1:3])
969 976 minutes = int(tz[3:5])
970 977 return -sign * (hours * 60 + minutes) * 60
971 978 if tz == "GMT" or tz == "UTC":
972 979 return 0
973 980 return None
974 981
975 982 # NOTE: unixtime = localunixtime + offset
976 983 offset, date = timezone(string), string
977 984 if offset != None:
978 985 date = " ".join(string.split()[:-1])
979 986
980 987 # add missing elements from defaults
981 988 for part in defaults:
982 989 found = [True for p in part if ("%"+p) in format]
983 990 if not found:
984 991 date += "@" + defaults[part]
985 992 format += "@%" + part[0]
986 993
987 994 timetuple = time.strptime(date, format)
988 995 localunixtime = int(calendar.timegm(timetuple))
989 996 if offset is None:
990 997 # local timezone
991 998 unixtime = int(time.mktime(timetuple))
992 999 offset = unixtime - localunixtime
993 1000 else:
994 1001 unixtime = localunixtime + offset
995 1002 return unixtime, offset
996 1003
997 1004 def parsedate(date, formats=None, defaults=None):
998 1005 """parse a localized date/time string and return a (unixtime, offset) tuple.
999 1006
1000 1007 The date may be a "unixtime offset" string or in one of the specified
1001 1008 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1002 1009 """
1003 1010 if not date:
1004 1011 return 0, 0
1005 1012 if isinstance(date, tuple) and len(date) == 2:
1006 1013 return date
1007 1014 if not formats:
1008 1015 formats = defaultdateformats
1009 1016 date = date.strip()
1010 1017 try:
1011 1018 when, offset = map(int, date.split(' '))
1012 1019 except ValueError:
1013 1020 # fill out defaults
1014 1021 if not defaults:
1015 1022 defaults = {}
1016 1023 now = makedate()
1017 1024 for part in "d mb yY HI M S".split():
1018 1025 if part not in defaults:
1019 1026 if part[0] in "HMS":
1020 1027 defaults[part] = "00"
1021 1028 else:
1022 1029 defaults[part] = datestr(now, "%" + part[0])
1023 1030
1024 1031 for format in formats:
1025 1032 try:
1026 1033 when, offset = strdate(date, format, defaults)
1027 1034 except (ValueError, OverflowError):
1028 1035 pass
1029 1036 else:
1030 1037 break
1031 1038 else:
1032 1039 raise Abort(_('invalid date: %r ') % date)
1033 1040 # validate explicit (probably user-specified) date and
1034 1041 # time zone offset. values must fit in signed 32 bits for
1035 1042 # current 32-bit linux runtimes. timezones go from UTC-12
1036 1043 # to UTC+14
1037 1044 if abs(when) > 0x7fffffff:
1038 1045 raise Abort(_('date exceeds 32 bits: %d') % when)
1039 1046 if offset < -50400 or offset > 43200:
1040 1047 raise Abort(_('impossible time zone offset: %d') % offset)
1041 1048 return when, offset
1042 1049
1043 1050 def matchdate(date):
1044 1051 """Return a function that matches a given date match specifier
1045 1052
1046 1053 Formats include:
1047 1054
1048 1055 '{date}' match a given date to the accuracy provided
1049 1056
1050 1057 '<{date}' on or before a given date
1051 1058
1052 1059 '>{date}' on or after a given date
1053 1060
1054 1061 """
1055 1062
1056 1063 def lower(date):
1057 1064 d = dict(mb="1", d="1")
1058 1065 return parsedate(date, extendeddateformats, d)[0]
1059 1066
1060 1067 def upper(date):
1061 1068 d = dict(mb="12", HI="23", M="59", S="59")
1062 1069 for days in "31 30 29".split():
1063 1070 try:
1064 1071 d["d"] = days
1065 1072 return parsedate(date, extendeddateformats, d)[0]
1066 1073 except:
1067 1074 pass
1068 1075 d["d"] = "28"
1069 1076 return parsedate(date, extendeddateformats, d)[0]
1070 1077
1071 1078 date = date.strip()
1072 1079 if date[0] == "<":
1073 1080 when = upper(date[1:])
1074 1081 return lambda x: x <= when
1075 1082 elif date[0] == ">":
1076 1083 when = lower(date[1:])
1077 1084 return lambda x: x >= when
1078 1085 elif date[0] == "-":
1079 1086 try:
1080 1087 days = int(date[1:])
1081 1088 except ValueError:
1082 1089 raise Abort(_("invalid day spec: %s") % date[1:])
1083 1090 when = makedate()[0] - days * 3600 * 24
1084 1091 return lambda x: x >= when
1085 1092 elif " to " in date:
1086 1093 a, b = date.split(" to ")
1087 1094 start, stop = lower(a), upper(b)
1088 1095 return lambda x: x >= start and x <= stop
1089 1096 else:
1090 1097 start, stop = lower(date), upper(date)
1091 1098 return lambda x: x >= start and x <= stop
1092 1099
1093 1100 def shortuser(user):
1094 1101 """Return a short representation of a user name or email address."""
1095 1102 f = user.find('@')
1096 1103 if f >= 0:
1097 1104 user = user[:f]
1098 1105 f = user.find('<')
1099 1106 if f >= 0:
1100 1107 user = user[f + 1:]
1101 1108 f = user.find(' ')
1102 1109 if f >= 0:
1103 1110 user = user[:f]
1104 1111 f = user.find('.')
1105 1112 if f >= 0:
1106 1113 user = user[:f]
1107 1114 return user
1108 1115
1109 1116 def email(author):
1110 1117 '''get email of author.'''
1111 1118 r = author.find('>')
1112 1119 if r == -1:
1113 1120 r = None
1114 1121 return author[author.find('<') + 1:r]
1115 1122
1116 1123 def ellipsis(text, maxlength=400):
1117 1124 """Trim string to at most maxlength (default: 400) characters."""
1118 1125 if len(text) <= maxlength:
1119 1126 return text
1120 1127 else:
1121 1128 return "%s..." % (text[:maxlength - 3])
1122 1129
1123 1130 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1124 1131 '''yield every hg repository under path, recursively.'''
1125 1132 def errhandler(err):
1126 1133 if err.filename == path:
1127 1134 raise err
1128 1135 if followsym and hasattr(os.path, 'samestat'):
1129 1136 def _add_dir_if_not_there(dirlst, dirname):
1130 1137 match = False
1131 1138 samestat = os.path.samestat
1132 1139 dirstat = os.stat(dirname)
1133 1140 for lstdirstat in dirlst:
1134 1141 if samestat(dirstat, lstdirstat):
1135 1142 match = True
1136 1143 break
1137 1144 if not match:
1138 1145 dirlst.append(dirstat)
1139 1146 return not match
1140 1147 else:
1141 1148 followsym = False
1142 1149
1143 1150 if (seen_dirs is None) and followsym:
1144 1151 seen_dirs = []
1145 1152 _add_dir_if_not_there(seen_dirs, path)
1146 1153 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1147 1154 dirs.sort()
1148 1155 if '.hg' in dirs:
1149 1156 yield root # found a repository
1150 1157 qroot = os.path.join(root, '.hg', 'patches')
1151 1158 if os.path.isdir(os.path.join(qroot, '.hg')):
1152 1159 yield qroot # we have a patch queue repo here
1153 1160 if recurse:
1154 1161 # avoid recursing inside the .hg directory
1155 1162 dirs.remove('.hg')
1156 1163 else:
1157 1164 dirs[:] = [] # don't descend further
1158 1165 elif followsym:
1159 1166 newdirs = []
1160 1167 for d in dirs:
1161 1168 fname = os.path.join(root, d)
1162 1169 if _add_dir_if_not_there(seen_dirs, fname):
1163 1170 if os.path.islink(fname):
1164 1171 for hgname in walkrepos(fname, True, seen_dirs):
1165 1172 yield hgname
1166 1173 else:
1167 1174 newdirs.append(d)
1168 1175 dirs[:] = newdirs
1169 1176
1170 1177 _rcpath = None
1171 1178
1172 1179 def os_rcpath():
1173 1180 '''return default os-specific hgrc search path'''
1174 1181 path = system_rcpath()
1175 1182 path.extend(user_rcpath())
1176 1183 path = [os.path.normpath(f) for f in path]
1177 1184 return path
1178 1185
1179 1186 def rcpath():
1180 1187 '''return hgrc search path. if env var HGRCPATH is set, use it.
1181 1188 for each item in path, if directory, use files ending in .rc,
1182 1189 else use item.
1183 1190 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1184 1191 if no HGRCPATH, use default os-specific path.'''
1185 1192 global _rcpath
1186 1193 if _rcpath is None:
1187 1194 if 'HGRCPATH' in os.environ:
1188 1195 _rcpath = []
1189 1196 for p in os.environ['HGRCPATH'].split(os.pathsep):
1190 1197 if not p:
1191 1198 continue
1192 1199 p = expandpath(p)
1193 1200 if os.path.isdir(p):
1194 1201 for f, kind in osutil.listdir(p):
1195 1202 if f.endswith('.rc'):
1196 1203 _rcpath.append(os.path.join(p, f))
1197 1204 else:
1198 1205 _rcpath.append(p)
1199 1206 else:
1200 1207 _rcpath = os_rcpath()
1201 1208 return _rcpath
1202 1209
1203 1210 def bytecount(nbytes):
1204 1211 '''return byte count formatted as readable string, with units'''
1205 1212
1206 1213 units = (
1207 1214 (100, 1 << 30, _('%.0f GB')),
1208 1215 (10, 1 << 30, _('%.1f GB')),
1209 1216 (1, 1 << 30, _('%.2f GB')),
1210 1217 (100, 1 << 20, _('%.0f MB')),
1211 1218 (10, 1 << 20, _('%.1f MB')),
1212 1219 (1, 1 << 20, _('%.2f MB')),
1213 1220 (100, 1 << 10, _('%.0f KB')),
1214 1221 (10, 1 << 10, _('%.1f KB')),
1215 1222 (1, 1 << 10, _('%.2f KB')),
1216 1223 (1, 1, _('%.0f bytes')),
1217 1224 )
1218 1225
1219 1226 for multiplier, divisor, format in units:
1220 1227 if nbytes >= divisor * multiplier:
1221 1228 return format % (nbytes / float(divisor))
1222 1229 return units[-1][2] % nbytes
1223 1230
1224 1231 def drop_scheme(scheme, path):
1225 1232 sc = scheme + ':'
1226 1233 if path.startswith(sc):
1227 1234 path = path[len(sc):]
1228 1235 if path.startswith('//'):
1229 1236 if scheme == 'file':
1230 1237 i = path.find('/', 2)
1231 1238 if i == -1:
1232 1239 return ''
1233 1240 # On Windows, absolute paths are rooted at the current drive
1234 1241 # root. On POSIX they are rooted at the file system root.
1235 1242 if os.name == 'nt':
1236 1243 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1237 1244 path = os.path.join(droot, path[i + 1:])
1238 1245 else:
1239 1246 path = path[i:]
1240 1247 else:
1241 1248 path = path[2:]
1242 1249 return path
1243 1250
1244 1251 def uirepr(s):
1245 1252 # Avoid double backslash in Windows path repr()
1246 1253 return repr(s).replace('\\\\', '\\')
1247 1254
1248 1255 def termwidth():
1249 1256 if 'COLUMNS' in os.environ:
1250 1257 try:
1251 1258 return int(os.environ['COLUMNS'])
1252 1259 except ValueError:
1253 1260 pass
1254 1261 try:
1255 1262 import termios, array, fcntl
1256 1263 for dev in (sys.stdout, sys.stdin):
1257 1264 try:
1258 1265 try:
1259 1266 fd = dev.fileno()
1260 1267 except AttributeError:
1261 1268 continue
1262 1269 if not os.isatty(fd):
1263 1270 continue
1264 1271 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1265 1272 return array.array('h', arri)[1]
1266 1273 except ValueError:
1267 1274 pass
1268 1275 except IOError, e:
1269 1276 if e[0] == errno.EINVAL:
1270 1277 pass
1271 1278 else:
1272 1279 raise
1273 1280 except ImportError:
1274 1281 pass
1275 1282 return 80
1276 1283
1277 1284 def wrap(line, hangindent, width=None):
1278 1285 if width is None:
1279 1286 width = termwidth() - 2
1280 1287 if width <= hangindent:
1281 1288 # adjust for weird terminal size
1282 1289 width = max(78, hangindent + 1)
1283 1290 padding = '\n' + ' ' * hangindent
1284 1291 # To avoid corrupting multi-byte characters in line, we must wrap
1285 1292 # a Unicode string instead of a bytestring.
1286 1293 try:
1287 1294 u = line.decode(encoding.encoding)
1288 1295 w = padding.join(textwrap.wrap(u, width=width - hangindent))
1289 1296 return w.encode(encoding.encoding)
1290 1297 except UnicodeDecodeError:
1291 1298 return padding.join(textwrap.wrap(line, width=width - hangindent))
1292 1299
1293 1300 def iterlines(iterator):
1294 1301 for chunk in iterator:
1295 1302 for line in chunk.splitlines():
1296 1303 yield line
1297 1304
1298 1305 def expandpath(path):
1299 1306 return os.path.expanduser(os.path.expandvars(path))
1300 1307
1301 1308 def hgcmd():
1302 1309 """Return the command used to execute current hg
1303 1310
1304 1311 This is different from hgexecutable() because on Windows we want
1305 1312 to avoid things opening new shell windows like batch files, so we
1306 1313 get either the python call or current executable.
1307 1314 """
1308 1315 if main_is_frozen():
1309 1316 return [sys.executable]
1310 1317 return gethgcmd()
1311 1318
1312 1319 def rundetached(args, condfn):
1313 1320 """Execute the argument list in a detached process.
1314 1321
1315 1322 condfn is a callable which is called repeatedly and should return
1316 1323 True once the child process is known to have started successfully.
1317 1324 At this point, the child process PID is returned. If the child
1318 1325 process fails to start or finishes before condfn() evaluates to
1319 1326 True, return -1.
1320 1327 """
1321 1328 # Windows case is easier because the child process is either
1322 1329 # successfully starting and validating the condition or exiting
1323 1330 # on failure. We just poll on its PID. On Unix, if the child
1324 1331 # process fails to start, it will be left in a zombie state until
1325 1332 # the parent wait on it, which we cannot do since we expect a long
1326 1333 # running process on success. Instead we listen for SIGCHLD telling
1327 1334 # us our child process terminated.
1328 1335 terminated = set()
1329 1336 def handler(signum, frame):
1330 1337 terminated.add(os.wait())
1331 1338 prevhandler = None
1332 1339 if hasattr(signal, 'SIGCHLD'):
1333 1340 prevhandler = signal.signal(signal.SIGCHLD, handler)
1334 1341 try:
1335 1342 pid = spawndetached(args)
1336 1343 while not condfn():
1337 1344 if ((pid in terminated or not testpid(pid))
1338 1345 and not condfn()):
1339 1346 return -1
1340 1347 time.sleep(0.1)
1341 1348 return pid
1342 1349 finally:
1343 1350 if prevhandler is not None:
1344 1351 signal.signal(signal.SIGCHLD, prevhandler)
1345 1352
1346 1353 try:
1347 1354 any, all = any, all
1348 1355 except NameError:
1349 1356 def any(iterable):
1350 1357 for i in iterable:
1351 1358 if i:
1352 1359 return True
1353 1360 return False
1354 1361
1355 1362 def all(iterable):
1356 1363 for i in iterable:
1357 1364 if not i:
1358 1365 return False
1359 1366 return True
General Comments 0
You need to be logged in to leave comments. Login now