diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2011-10-01 20:49:36 +0000 |
---|---|---|
committer | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-09-27 13:27:51 +0000 |
commit | 921ced43c48c1d170452a7b251b94cc96ec8dd44 (patch) | |
tree | 3c4a89176ea67fe4c7bf7b375488361a823c95fa /mercurial/util.py | |
parent | 9039c805b0a7e36220101323f82735f08a104b37 (diff) | |
download | mercurial-tarball-master.tar.gz |
Imported from /srv/lorry/lorry-area/mercurial-tarball/mercurial-1.9.3.tar.gz.HEADmercurial-1.9.3master
Diffstat (limited to 'mercurial/util.py')
-rw-r--r-- | mercurial/util.py | 400 |
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() |