summaryrefslogtreecommitdiff
path: root/mercurial/util.py
diff options
context:
space:
mode:
Diffstat (limited to 'mercurial/util.py')
-rw-r--r--mercurial/util.py400
1 files changed, 152 insertions, 248 deletions
diff --git a/mercurial/util.py b/mercurial/util.py
index 4a6e215..4597ffb 100644
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -14,85 +14,21 @@ hide platform-specific details from the core.
"""
from i18n import _
-import error, osutil, encoding, collections
+import error, osutil, encoding
import errno, re, shutil, sys, tempfile, traceback
-import os, time, datetime, calendar, textwrap, signal
+import os, time, calendar, textwrap, signal
import imp, socket, urllib
-if os.name == 'nt':
- import windows as platform
-else:
- import posix as platform
-
-cachestat = platform.cachestat
-checkexec = platform.checkexec
-checklink = platform.checklink
-copymode = platform.copymode
-executablepath = platform.executablepath
-expandglobs = platform.expandglobs
-explainexit = platform.explainexit
-findexe = platform.findexe
-gethgcmd = platform.gethgcmd
-getuser = platform.getuser
-groupmembers = platform.groupmembers
-groupname = platform.groupname
-hidewindow = platform.hidewindow
-isexec = platform.isexec
-isowner = platform.isowner
-localpath = platform.localpath
-lookupreg = platform.lookupreg
-makedir = platform.makedir
-nlinks = platform.nlinks
-normpath = platform.normpath
-normcase = platform.normcase
-nulldev = platform.nulldev
-openhardlinks = platform.openhardlinks
-oslink = platform.oslink
-parsepatchoutput = platform.parsepatchoutput
-pconvert = platform.pconvert
-popen = platform.popen
-posixfile = platform.posixfile
-quotecommand = platform.quotecommand
-realpath = platform.realpath
-rename = platform.rename
-samedevice = platform.samedevice
-samefile = platform.samefile
-samestat = platform.samestat
-setbinary = platform.setbinary
-setflags = platform.setflags
-setsignalhandler = platform.setsignalhandler
-shellquote = platform.shellquote
-spawndetached = platform.spawndetached
-sshargs = platform.sshargs
-statfiles = platform.statfiles
-termwidth = platform.termwidth
-testpid = platform.testpid
-umask = platform.umask
-unlink = platform.unlink
-unlinkpath = platform.unlinkpath
-username = platform.username
-
# Python compatibility
-_notset = object()
+def sha1(s):
+ return _fastsha1(s)
+_notset = object()
def safehasattr(thing, attr):
return getattr(thing, attr, _notset) is not _notset
-def sha1(s=''):
- '''
- Low-overhead wrapper around Python's SHA support
-
- >>> f = _fastsha1
- >>> a = sha1()
- >>> a = f()
- >>> a.hexdigest()
- 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
- '''
-
- return _fastsha1(s)
-
-def _fastsha1(s=''):
+def _fastsha1(s):
# This function will import sha1 from hashlib or sha (whichever is
# available) and overwrite itself with it on the first call.
# Subsequent calls will go directly to the imported function.
@@ -104,15 +40,18 @@ def _fastsha1(s=''):
_fastsha1 = sha1 = _sha1
return _sha1(s)
+import __builtin__
+
+if sys.version_info[0] < 3:
+ def fakebuffer(sliceable, offset=0):
+ return sliceable[offset:]
+else:
+ def fakebuffer(sliceable, offset=0):
+ return memoryview(sliceable)[offset:]
try:
- buffer = buffer
+ buffer
except NameError:
- if sys.version_info[0] < 3:
- def buffer(sliceable, offset=0):
- return sliceable[offset:]
- else:
- def buffer(sliceable, offset=0):
- return memoryview(sliceable)[offset:]
+ __builtin__.buffer = fakebuffer
import subprocess
closefds = os.name == 'posix'
@@ -199,27 +138,15 @@ def cachefunc(func):
return f
-try:
- collections.deque.remove
- deque = collections.deque
-except AttributeError:
- # python 2.4 lacks deque.remove
- class deque(collections.deque):
- def remove(self, val):
- for i, v in enumerate(self):
- if v == val:
- del self[i]
- break
-
def lrucachefunc(func):
'''cache most recent results of function calls'''
cache = {}
- order = deque()
+ order = []
if func.func_code.co_argcount == 1:
def f(arg):
if arg not in cache:
if len(cache) > 20:
- del cache[order.popleft()]
+ del cache[order.pop(0)]
cache[arg] = func(arg)
else:
order.remove(arg)
@@ -229,7 +156,7 @@ def lrucachefunc(func):
def f(*args):
if args not in cache:
if len(cache) > 20:
- del cache[order.popleft()]
+ del cache[order.pop(0)]
cache[args] = func(*args)
else:
order.remove(args)
@@ -380,8 +307,8 @@ def mainfrozen():
The code supports py2exe (most common, Windows only) and tools/freeze
(portable, not much used).
"""
- return (safehasattr(sys, "frozen") or # new py2exe
- safehasattr(sys, "importers") or # old py2exe
+ return (hasattr(sys, "frozen") or # new py2exe
+ hasattr(sys, "importers") or # old py2exe
imp.is_frozen("__main__")) # tools/freeze
def hgexecutable():
@@ -391,13 +318,10 @@ def hgexecutable():
"""
if _hgexecutable is None:
hg = os.environ.get('HG')
- mainmod = sys.modules['__main__']
if hg:
_sethgexecutable(hg)
elif mainfrozen():
_sethgexecutable(sys.executable)
- elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
- _sethgexecutable(mainmod.__file__)
else:
exe = findexe('hg') or os.path.basename(sys.argv[0])
_sethgexecutable(exe)
@@ -431,29 +355,22 @@ def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
return str(val)
origcmd = cmd
cmd = quotecommand(cmd)
- if sys.platform == 'plan9':
- # subprocess kludge to work around issues in half-baked Python
- # ports, notably bichued/python:
- if not cwd is None:
- os.chdir(cwd)
- rc = os.system(cmd)
+ env = dict(os.environ)
+ env.update((k, py2shell(v)) for k, v in environ.iteritems())
+ env['HG'] = hgexecutable()
+ if out is None or out == sys.__stdout__:
+ rc = subprocess.call(cmd, shell=True, close_fds=closefds,
+ env=env, cwd=cwd)
else:
- env = dict(os.environ)
- env.update((k, py2shell(v)) for k, v in environ.iteritems())
- env['HG'] = hgexecutable()
- if out is None or out == sys.__stdout__:
- rc = subprocess.call(cmd, shell=True, close_fds=closefds,
- env=env, cwd=cwd)
- else:
- proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
- env=env, cwd=cwd, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- for line in proc.stdout:
- out.write(line)
- proc.wait()
- rc = proc.returncode
- if sys.platform == 'OpenVMS' and rc & 1:
- rc = 0
+ proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
+ env=env, cwd=cwd, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ for line in proc.stdout:
+ out.write(line)
+ proc.wait()
+ rc = proc.returncode
+ if sys.platform == 'OpenVMS' and rc & 1:
+ rc = 0
if rc and onerr:
errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
explainexit(rc)[0])
@@ -477,6 +394,18 @@ def checksignature(func):
return check
+def makedir(path, notindexed):
+ os.mkdir(path)
+
+def unlinkpath(f):
+ """unlink and remove the directory if it is empty"""
+ os.unlink(f)
+ # try removing directories that might now be empty
+ try:
+ os.removedirs(os.path.dirname(f))
+ except OSError:
+ pass
+
def copyfile(src, dest):
"copy a file, preserving mode and atime/mtime"
if os.path.islink(src):
@@ -542,7 +471,6 @@ def checkwinfilename(path):
"filename contains '\\\\x07', which is invalid on Windows"
>>> checkwinfilename("foo/bar/bla ")
"filename ends with ' ', which is not allowed on Windows"
- >>> checkwinfilename("../bar")
'''
for n in path.replace('\\', '/').split('/'):
if not n:
@@ -559,14 +487,26 @@ def checkwinfilename(path):
return _("filename contains '%s', which is reserved "
"on Windows") % base
t = n[-1]
- if t in '. ' and n not in '..':
+ if t in '. ':
return _("filename ends with '%s', which is not allowed "
"on Windows") % t
+def lookupreg(key, name=None, scope=None):
+ return None
+
+def hidewindow():
+ """Hide current shell window.
+
+ Used to hide the window opened when starting asynchronous
+ child process under Windows, unneeded on other systems.
+ """
+ pass
+
if os.name == 'nt':
checkosfilename = checkwinfilename
+ from windows import *
else:
- checkosfilename = platform.checkosfilename
+ from posix import *
def makelock(info, pathname):
try:
@@ -612,12 +552,9 @@ def checkcase(path):
"""
s1 = os.stat(path)
d, b = os.path.split(path)
- b2 = b.upper()
- if b == b2:
- b2 = b.lower()
- if b == b2:
- return True # no evidence against case sensitivity
- p2 = os.path.join(d, b2)
+ p2 = os.path.join(d, b.upper())
+ if path == p2:
+ p2 = os.path.join(d, b.lower())
try:
s2 = os.stat(p2)
if s2 == s1:
@@ -626,45 +563,22 @@ def checkcase(path):
except OSError:
return True
-try:
- import re2
- _re2 = None
-except ImportError:
- _re2 = False
-
-def compilere(pat):
- '''Compile a regular expression, using re2 if possible
-
- For best performance, use only re2-compatible regexp features.'''
- global _re2
- if _re2 is None:
- try:
- re2.compile
- _re2 = True
- except ImportError:
- _re2 = False
- if _re2:
- try:
- return re2.compile(pat)
- except re2.error:
- pass
- return re.compile(pat)
-
_fspathcache = {}
def fspath(name, root):
'''Get name in the case stored in the filesystem
- The name should be relative to root, and be normcase-ed for efficiency.
-
- Note that this function is unnecessary, and should not be
+ The name is either relative to root, or it is an absolute path starting
+ with root. Note that this function is unnecessary, and should not be
called, for case-sensitive filesystems (simply because it's expensive).
-
- The root should be normcase-ed, too.
'''
- def find(p, contents):
- for n in contents:
- if normcase(n) == p:
- return n
+ # If name is absolute, make it relative
+ if name.lower().startswith(root.lower()):
+ l = len(root)
+ if name[l] == os.sep or name[l] == os.altsep:
+ l = l + 1
+ name = name[l:]
+
+ if not os.path.lexists(os.path.join(root, name)):
return None
seps = os.sep
@@ -673,7 +587,7 @@ def fspath(name, root):
# Protect backslashes. This gets silly very quickly.
seps.replace('\\','\\\\')
pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
- dir = os.path.normpath(root)
+ dir = os.path.normcase(os.path.normpath(root))
result = []
for part, sep in pattern.findall(name):
if sep:
@@ -684,16 +598,16 @@ def fspath(name, root):
_fspathcache[dir] = os.listdir(dir)
contents = _fspathcache[dir]
- found = find(part, contents)
- if not found:
- # retry "once per directory" per "dirstate.walk" which
- # may take place for each patches of "hg qpush", for example
- contents = os.listdir(dir)
- _fspathcache[dir] = contents
- found = find(part, contents)
-
- result.append(found or part)
- dir = os.path.join(dir, part)
+ lpart = part.lower()
+ lenp = len(part)
+ for n in contents:
+ if lenp == len(n) and n.lower() == lpart:
+ result.append(n)
+ break
+ else:
+ # Cannot happen, as the file exists!
+ result.append(part)
+ dir = os.path.join(dir, lpart)
return ''.join(result)
@@ -776,7 +690,16 @@ def mktempcopy(name, emptyok=False, createmode=None):
# Temporary files are created with mode 0600, which is usually not
# what we want. If the original file already exists, just copy
# its mode. Otherwise, manually obey umask.
- copymode(name, temp, createmode)
+ try:
+ st_mode = os.lstat(name).st_mode & 0777
+ except OSError, inst:
+ if inst.errno != errno.ENOENT:
+ raise
+ st_mode = createmode
+ if st_mode is None:
+ st_mode = ~umask
+ st_mode &= 0666
+ os.chmod(temp, st_mode)
if emptyok:
return temp
try:
@@ -793,9 +716,9 @@ def mktempcopy(name, emptyok=False, createmode=None):
ofp.write(chunk)
ifp.close()
ofp.close()
- except: # re-raises
+ except:
try: os.unlink(temp)
- except OSError: pass
+ except: pass
raise
return temp
@@ -803,10 +726,11 @@ class atomictempfile(object):
'''writeable file object that atomically updates a file
All writes will go to a temporary copy of the original file. Call
- close() when you are done writing, and atomictempfile will rename
- the temporary copy to the original name, making the changes
- visible. If the object is destroyed without being closed, all your
- writes are discarded.
+ rename() when you are done writing, and atomictempfile will rename
+ the temporary copy to the original name, making the changes visible.
+
+ Unlike other file-like objects, close() discards your writes by
+ simply deleting the temporary file.
'''
def __init__(self, name, mode='w+b', createmode=None):
self.__name = name # permanent name
@@ -816,16 +740,14 @@ class atomictempfile(object):
# delegated methods
self.write = self._fp.write
- self.seek = self._fp.seek
- self.tell = self._fp.tell
self.fileno = self._fp.fileno
- def close(self):
+ def rename(self):
if not self._fp.closed:
self._fp.close()
rename(self._tempname, localpath(self.__name))
- def discard(self):
+ def close(self):
if not self._fp.closed:
try:
os.unlink(self._tempname)
@@ -834,25 +756,24 @@ class atomictempfile(object):
self._fp.close()
def __del__(self):
- if safehasattr(self, '_fp'): # constructor actually did something
- self.discard()
+ if hasattr(self, '_fp'): # constructor actually did something
+ self.close()
def makedirs(name, mode=None):
"""recursive directory creation with parent mode inheritance"""
+ parent = os.path.abspath(os.path.dirname(name))
try:
os.mkdir(name)
+ if mode is not None:
+ os.chmod(name, mode)
+ return
except OSError, err:
if err.errno == errno.EEXIST:
return
- if err.errno != errno.ENOENT or not name:
- raise
- parent = os.path.dirname(os.path.abspath(name))
- if parent == name:
+ if not name or parent == name or err.errno != errno.ENOENT:
raise
- makedirs(parent, mode)
- os.mkdir(name)
- if mode is not None:
- os.chmod(name, mode)
+ makedirs(parent, mode)
+ makedirs(name, mode)
def readfile(path):
fp = open(path, 'rb')
@@ -893,7 +814,7 @@ class chunkbuffer(object):
else:
yield chunk
self.iter = splitbig(in_iter)
- self._queue = deque()
+ self._queue = []
def read(self, l):
"""Read L bytes of data from the iterator of chunks of data.
@@ -913,10 +834,10 @@ class chunkbuffer(object):
if not queue:
break
- chunk = queue.popleft()
+ chunk = queue.pop(0)
left -= len(chunk)
if left < 0:
- queue.appendleft(chunk[left:])
+ queue.insert(0, chunk[left:])
buf += chunk[:left]
else:
buf += chunk
@@ -945,14 +866,16 @@ def filechunkiter(f, size=65536, limit=None):
yield s
def makedate():
- ct = time.time()
- if ct < 0:
+ lt = time.localtime()
+ if lt[8] == 1 and time.daylight:
+ tz = time.altzone
+ else:
+ tz = time.timezone
+ t = time.mktime(lt)
+ if t < 0:
hint = _("check your clock")
- raise Abort(_("negative timestamp: %d") % ct, hint=hint)
- delta = (datetime.datetime.utcfromtimestamp(ct) -
- datetime.datetime.fromtimestamp(ct))
- tz = delta.days * 86400 + delta.seconds
- return ct, tz
+ raise Abort(_("negative timestamp: %d") % t, hint=hint)
+ return t, tz
def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
"""represent a (unixtime, offset) tuple as a localized time.
@@ -1114,7 +1037,7 @@ def matchdate(date):
try:
d["d"] = days
return parsedate(date, extendeddateformats, d)[0]
- except Abort:
+ except:
pass
d["d"] = "28"
return parsedate(date, extendeddateformats, d)[0]
@@ -1167,16 +1090,6 @@ def shortuser(user):
user = user[:f]
return user
-def emailuser(user):
- """Return the user portion of an email address."""
- f = user.find('@')
- if f >= 0:
- user = user[:f]
- f = user.find('<')
- if f >= 0:
- user = user[f + 1:]
- return user
-
def email(author):
'''get email of author.'''
r = author.find('>')
@@ -1202,26 +1115,26 @@ def ellipsis(text, maxlength=400):
except (UnicodeDecodeError, UnicodeEncodeError):
return _ellipsis(text, maxlength)[0]
-_byteunits = (
- (100, 1 << 30, _('%.0f GB')),
- (10, 1 << 30, _('%.1f GB')),
- (1, 1 << 30, _('%.2f GB')),
- (100, 1 << 20, _('%.0f MB')),
- (10, 1 << 20, _('%.1f MB')),
- (1, 1 << 20, _('%.2f MB')),
- (100, 1 << 10, _('%.0f KB')),
- (10, 1 << 10, _('%.1f KB')),
- (1, 1 << 10, _('%.2f KB')),
- (1, 1, _('%.0f bytes')),
- )
-
def bytecount(nbytes):
'''return byte count formatted as readable string, with units'''
- for multiplier, divisor, format in _byteunits:
+ units = (
+ (100, 1 << 30, _('%.0f GB')),
+ (10, 1 << 30, _('%.1f GB')),
+ (1, 1 << 30, _('%.2f GB')),
+ (100, 1 << 20, _('%.0f MB')),
+ (10, 1 << 20, _('%.1f MB')),
+ (1, 1 << 20, _('%.2f MB')),
+ (100, 1 << 10, _('%.0f KB')),
+ (10, 1 << 10, _('%.1f KB')),
+ (1, 1 << 10, _('%.2f KB')),
+ (1, 1, _('%.0f bytes')),
+ )
+
+ for multiplier, divisor, format in units:
if nbytes >= divisor * multiplier:
return format % (nbytes / float(divisor))
- return _byteunits[-1][2] % nbytes
+ return units[-1][2] % nbytes
def uirepr(s):
# Avoid double backslash in Windows path repr()
@@ -1390,9 +1303,8 @@ def rundetached(args, condfn):
def handler(signum, frame):
terminated.add(os.wait())
prevhandler = None
- SIGCHLD = getattr(signal, 'SIGCHLD', None)
- if SIGCHLD is not None:
- prevhandler = signal.signal(SIGCHLD, handler)
+ if hasattr(signal, 'SIGCHLD'):
+ prevhandler = signal.signal(signal.SIGCHLD, handler)
try:
pid = spawndetached(args)
while not condfn():
@@ -1550,7 +1462,7 @@ class url(object):
"""
_safechars = "!~*'()+"
- _safepchars = "/!~*'()+:"
+ _safepchars = "/!~*'()+"
_matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
def __init__(self, path, parsequery=True, parsefragment=True):
@@ -1662,8 +1574,8 @@ class url(object):
Examples:
- >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
- 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
+ >>> str(url('http://user:pw@host:80/?foo#bar'))
+ 'http://user:pw@host:80/?foo#bar'
>>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
'http://user:pw@host:80/?foo=bar&baz=42'
>>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
@@ -1684,8 +1596,6 @@ class url(object):
'path'
>>> str(url('file:///tmp/foo/bar'))
'file:///tmp/foo/bar'
- >>> str(url('file:///c:/tmp/foo/bar'))
- 'file:///c:/tmp/foo/bar'
>>> print url(r'bundle:foo\bar')
bundle:foo\bar
"""
@@ -1700,11 +1610,8 @@ class url(object):
s = self.scheme + ':'
if self.user or self.passwd or self.host:
s += '//'
- elif self.scheme and (not self.path or self.path.startswith('/')
- or hasdriveletter(self.path)):
+ elif self.scheme and (not self.path or self.path.startswith('/')):
s += '//'
- if hasdriveletter(self.path):
- s += '/'
if self.user:
s += urllib.quote(self.user, safe=self._safechars)
if self.passwd:
@@ -1741,10 +1648,8 @@ class url(object):
self.user, self.passwd = user, passwd
if not self.user:
return (s, None)
- # authinfo[1] is passed to urllib2 password manager, and its
- # URIs must not contain credentials. The host is passed in the
- # URIs list because Python < 2.4.3 uses only that to search for
- # a password.
+ # authinfo[1] is passed to urllib2 password manager, and its URIs
+ # must not contain credentials.
return (s, (None, (s, self.host),
self.user, self.passwd or ''))
@@ -1766,8 +1671,7 @@ class url(object):
# letters to paths with drive letters.
if hasdriveletter(self._hostport):
path = self._hostport + '/' + self.path
- elif (self.host is not None and self.path
- and not hasdriveletter(path)):
+ elif self.host is not None and self.path:
path = '/' + path
return path
return self._origpath
@@ -1776,7 +1680,7 @@ def hasscheme(path):
return bool(url(path).scheme)
def hasdriveletter(path):
- return path and path[1:2] == ':' and path[0:1].isalpha()
+ return path[1:2] == ':' and path[0:1].isalpha()
def urllocalpath(path):
return url(path, parsequery=False, parsefragment=False).localpath()