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