diff -r 57875cf423c9 -r 2372284d9457 mercurial/vfs.py --- a/mercurial/vfs.py Sat Oct 05 10:29:34 2019 -0400 +++ b/mercurial/vfs.py Sun Oct 06 09:45:02 2019 -0400 @@ -22,23 +22,26 @@ util, ) + def _avoidambig(path, oldstat): """Avoid file stat ambiguity forcibly This function causes copying ``path`` file, if it is owned by another (see issue5418 and issue5584 for detail). """ + def checkandavoid(): newstat = util.filestat.frompath(path) # return whether file stat ambiguity is (already) avoided - return (not newstat.isambig(oldstat) or - newstat.avoidambig(path, oldstat)) + return not newstat.isambig(oldstat) or newstat.avoidambig(path, oldstat) + if not checkandavoid(): # simply copy to change owner of path to get privilege to # advance mtime (see issue5418) util.rename(util.mktempcopy(path), path) checkandavoid() + class abstractvfs(object): """Abstract base class; cannot be instantiated""" @@ -173,8 +176,9 @@ return os.mkdir(self.join(path)) def mkstemp(self, suffix='', prefix='tmp', dir=None): - fd, name = pycompat.mkstemp(suffix=suffix, prefix=prefix, - dir=self.join(dir)) + fd, name = pycompat.mkstemp( + suffix=suffix, prefix=prefix, dir=self.join(dir) + ) dname, fname = util.split(name) if dir: return fd, os.path.join(dir, fname) @@ -227,6 +231,7 @@ If ``forcibly``, this tries to remove READ-ONLY files, too. """ if forcibly: + def onerror(function, path, excinfo): if function is not os.remove: raise @@ -236,10 +241,12 @@ raise os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE) os.remove(path) + else: onerror = None - return shutil.rmtree(self.join(path), - ignore_errors=ignore_errors, onerror=onerror) + return shutil.rmtree( + self.join(path), ignore_errors=ignore_errors, onerror=onerror + ) def setflags(self, path, l, x): return util.setflags(self.join(path), l, x) @@ -255,8 +262,9 @@ util.tryunlink(self.join(path)) def unlinkpath(self, path=None, ignoremissing=False, rmdir=True): - return util.unlinkpath(self.join(path), ignoremissing=ignoremissing, - rmdir=rmdir) + return util.unlinkpath( + self.join(path), ignoremissing=ignoremissing, rmdir=rmdir + ) def utime(self, path=None, t=None): return os.utime(self.join(path), t) @@ -294,7 +302,8 @@ vfs = getattr(self, 'vfs', self) if getattr(vfs, '_backgroundfilecloser', None): raise error.Abort( - _('can only have 1 active background file closer')) + _('can only have 1 active background file closer') + ) with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc: try: @@ -303,6 +312,7 @@ finally: vfs._backgroundfilecloser = None + class vfs(abstractvfs): '''Operate files relative to a base directory @@ -313,8 +323,15 @@ (b) the base directory is managed by hg and considered sort-of append-only. See pathutil.pathauditor() for details. ''' - def __init__(self, base, audit=True, cacheaudited=False, expandpath=False, - realpath=False): + + def __init__( + self, + base, + audit=True, + cacheaudited=False, + expandpath=False, + realpath=False, + ): if expandpath: base = util.expandpath(base) if realpath: @@ -324,7 +341,7 @@ if audit: self.audit = pathutil.pathauditor(self.base, cached=cacheaudited) else: - self.audit = (lambda path, mode=None: True) + self.audit = lambda path, mode=None: True self.createmode = None self._trustnlink = None self.options = {} @@ -351,9 +368,17 @@ raise error.Abort("%s: %r" % (r, path)) self.audit(path, mode=mode) - def __call__(self, path, mode="r", atomictemp=False, notindexed=False, - backgroundclose=False, checkambig=False, auditpath=True, - makeparentdirs=True): + def __call__( + self, + path, + mode="r", + atomictemp=False, + notindexed=False, + backgroundclose=False, + checkambig=False, + auditpath=True, + makeparentdirs=True, + ): '''Open ``path`` file, which is relative to vfs root. By default, parent directories are created as needed. Newly created @@ -389,7 +414,7 @@ f = self.join(path) if "b" not in mode: - mode += "b" # for that other OS + mode += "b" # for that other OS nlink = -1 if mode not in ('r', 'rb'): @@ -400,8 +425,9 @@ if atomictemp: if makeparentdirs: util.makedirs(dirname, self.createmode, notindexed) - return util.atomictempfile(f, mode, self.createmode, - checkambig=checkambig) + return util.atomictempfile( + f, mode, self.createmode, checkambig=checkambig + ) try: if 'w' in mode: util.unlink(f) @@ -412,7 +438,7 @@ with util.posixfile(f): nlink = util.nlinks(f) if nlink < 1: - nlink = 2 # force mktempcopy (issue1922) + nlink = 2 # force mktempcopy (issue1922) except (OSError, IOError) as e: if e.errno != errno.ENOENT: raise @@ -430,16 +456,25 @@ if checkambig: if mode in ('r', 'rb'): - raise error.Abort(_('implementation error: mode %s is not' - ' valid for checkambig=True') % mode) + raise error.Abort( + _( + 'implementation error: mode %s is not' + ' valid for checkambig=True' + ) + % mode + ) fp = checkambigatclosing(fp) - if (backgroundclose and - isinstance(threading.currentThread(), threading._MainThread)): + if backgroundclose and isinstance( + threading.currentThread(), threading._MainThread + ): if not self._backgroundfilecloser: - raise error.Abort(_('backgroundclose can only be used when a ' - 'backgroundclosing context manager is active') - ) + raise error.Abort( + _( + 'backgroundclose can only be used when a ' + 'backgroundclosing context manager is active' + ) + ) fp = delayclosedfile(fp, self._backgroundfilecloser) @@ -456,9 +491,12 @@ try: os.symlink(src, linkname) except OSError as err: - raise OSError(err.errno, _('could not symlink to %r: %s') % - (src, encoding.strtolocal(err.strerror)), - linkname) + raise OSError( + err.errno, + _('could not symlink to %r: %s') + % (src, encoding.strtolocal(err.strerror)), + linkname, + ) else: self.write(dst, src) @@ -468,8 +506,10 @@ else: return self.base + opener = vfs + class proxyvfs(abstractvfs): def __init__(self, vfs): self.vfs = vfs @@ -485,6 +525,7 @@ def options(self, value): self.vfs.options = value + class filtervfs(proxyvfs, abstractvfs): '''Wrapper vfs for filtering filenames with a function.''' @@ -501,8 +542,10 @@ else: return self.vfs.join(path) + filteropener = filtervfs + class readonlyvfs(proxyvfs): '''Wrapper vfs preventing any writing.''' @@ -517,11 +560,13 @@ def join(self, path, *insidef): return self.vfs.join(path, *insidef) + class closewrapbase(object): """Base class of wrapper, which hooks closing Do not instantiate outside of the vfs layer. """ + def __init__(self, fh): object.__setattr__(self, r'_origfh', fh) @@ -544,11 +589,13 @@ def close(self): raise NotImplementedError('attempted instantiating ' + str(type(self))) + class delayclosedfile(closewrapbase): """Proxy for a file object whose close is delayed. Do not instantiate outside of the vfs layer. """ + def __init__(self, fh, closer): super(delayclosedfile, self).__init__(fh) object.__setattr__(self, r'_closer', closer) @@ -559,8 +606,10 @@ def close(self): self._closer.close(self._origfh) + class backgroundfilecloser(object): """Coordinates background closing of file handles on multiple threads.""" + def __init__(self, ui, expectedcount=-1): self._running = False self._entered = False @@ -587,8 +636,9 @@ maxqueue = ui.configint('worker', 'backgroundclosemaxqueue') threadcount = ui.configint('worker', 'backgroundclosethreadcount') - ui.debug('starting %d threads for background file closing\n' % - threadcount) + ui.debug( + 'starting %d threads for background file closing\n' % threadcount + ) self._queue = pycompat.queue.Queue(maxsize=maxqueue) self._running = True @@ -629,8 +679,9 @@ def close(self, fh): """Schedule a file for closing.""" if not self._entered: - raise error.Abort(_('can only call close() when context manager ' - 'active')) + raise error.Abort( + _('can only call close() when context manager ' 'active') + ) # If a background thread encountered an exception, raise now so we fail # fast. Otherwise we may potentially go on for minutes until the error @@ -647,6 +698,7 @@ self._queue.put(fh, block=True, timeout=None) + class checkambigatclosing(closewrapbase): """Proxy for a file object, to avoid ambiguity of file stat @@ -657,6 +709,7 @@ Do not instantiate outside of the vfs layer. """ + def __init__(self, fh): super(checkambigatclosing, self).__init__(fh) object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))