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