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