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