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