mercurial/vfs.py
changeset 31217 0f31830fbfc4
parent 31025 6cf2857526c7
child 31309 8908f985570c
equal deleted inserted replaced
31216:21fa3d3688f3 31217:0f31830fbfc4
       
     1 # vfs.py - Mercurial 'vfs' classes
       
     2 #
       
     3 #  Copyright Matt Mackall <mpm@selenic.com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 from __future__ import absolute_import
       
     8 
       
     9 import contextlib
       
    10 import errno
       
    11 import os
       
    12 import shutil
       
    13 import stat
       
    14 import tempfile
       
    15 import threading
       
    16 
       
    17 from .i18n import _
       
    18 from . import (
       
    19     error,
       
    20     osutil,
       
    21     pathutil,
       
    22     pycompat,
       
    23     util,
       
    24 )
       
    25 
       
    26 class abstractvfs(object):
       
    27     """Abstract base class; cannot be instantiated"""
       
    28 
       
    29     def __init__(self, *args, **kwargs):
       
    30         '''Prevent instantiation; don't call this from subclasses.'''
       
    31         raise NotImplementedError('attempted instantiating ' + str(type(self)))
       
    32 
       
    33     def tryread(self, path):
       
    34         '''gracefully return an empty string for missing files'''
       
    35         try:
       
    36             return self.read(path)
       
    37         except IOError as inst:
       
    38             if inst.errno != errno.ENOENT:
       
    39                 raise
       
    40         return ""
       
    41 
       
    42     def tryreadlines(self, path, mode='rb'):
       
    43         '''gracefully return an empty array for missing files'''
       
    44         try:
       
    45             return self.readlines(path, mode=mode)
       
    46         except IOError as inst:
       
    47             if inst.errno != errno.ENOENT:
       
    48                 raise
       
    49         return []
       
    50 
       
    51     @util.propertycache
       
    52     def open(self):
       
    53         '''Open ``path`` file, which is relative to vfs root.
       
    54 
       
    55         Newly created directories are marked as "not to be indexed by
       
    56         the content indexing service", if ``notindexed`` is specified
       
    57         for "write" mode access.
       
    58         '''
       
    59         return self.__call__
       
    60 
       
    61     def read(self, path):
       
    62         with self(path, 'rb') as fp:
       
    63             return fp.read()
       
    64 
       
    65     def readlines(self, path, mode='rb'):
       
    66         with self(path, mode=mode) as fp:
       
    67             return fp.readlines()
       
    68 
       
    69     def write(self, path, data, backgroundclose=False):
       
    70         with self(path, 'wb', backgroundclose=backgroundclose) as fp:
       
    71             return fp.write(data)
       
    72 
       
    73     def writelines(self, path, data, mode='wb', notindexed=False):
       
    74         with self(path, mode=mode, notindexed=notindexed) as fp:
       
    75             return fp.writelines(data)
       
    76 
       
    77     def append(self, path, data):
       
    78         with self(path, 'ab') as fp:
       
    79             return fp.write(data)
       
    80 
       
    81     def basename(self, path):
       
    82         """return base element of a path (as os.path.basename would do)
       
    83 
       
    84         This exists to allow handling of strange encoding if needed."""
       
    85         return os.path.basename(path)
       
    86 
       
    87     def chmod(self, path, mode):
       
    88         return os.chmod(self.join(path), mode)
       
    89 
       
    90     def dirname(self, path):
       
    91         """return dirname element of a path (as os.path.dirname would do)
       
    92 
       
    93         This exists to allow handling of strange encoding if needed."""
       
    94         return os.path.dirname(path)
       
    95 
       
    96     def exists(self, path=None):
       
    97         return os.path.exists(self.join(path))
       
    98 
       
    99     def fstat(self, fp):
       
   100         return util.fstat(fp)
       
   101 
       
   102     def isdir(self, path=None):
       
   103         return os.path.isdir(self.join(path))
       
   104 
       
   105     def isfile(self, path=None):
       
   106         return os.path.isfile(self.join(path))
       
   107 
       
   108     def islink(self, path=None):
       
   109         return os.path.islink(self.join(path))
       
   110 
       
   111     def isfileorlink(self, path=None):
       
   112         '''return whether path is a regular file or a symlink
       
   113 
       
   114         Unlike isfile, this doesn't follow symlinks.'''
       
   115         try:
       
   116             st = self.lstat(path)
       
   117         except OSError:
       
   118             return False
       
   119         mode = st.st_mode
       
   120         return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
       
   121 
       
   122     def reljoin(self, *paths):
       
   123         """join various elements of a path together (as os.path.join would do)
       
   124 
       
   125         The vfs base is not injected so that path stay relative. This exists
       
   126         to allow handling of strange encoding if needed."""
       
   127         return os.path.join(*paths)
       
   128 
       
   129     def split(self, path):
       
   130         """split top-most element of a path (as os.path.split would do)
       
   131 
       
   132         This exists to allow handling of strange encoding if needed."""
       
   133         return os.path.split(path)
       
   134 
       
   135     def lexists(self, path=None):
       
   136         return os.path.lexists(self.join(path))
       
   137 
       
   138     def lstat(self, path=None):
       
   139         return os.lstat(self.join(path))
       
   140 
       
   141     def listdir(self, path=None):
       
   142         return os.listdir(self.join(path))
       
   143 
       
   144     def makedir(self, path=None, notindexed=True):
       
   145         return util.makedir(self.join(path), notindexed)
       
   146 
       
   147     def makedirs(self, path=None, mode=None):
       
   148         return util.makedirs(self.join(path), mode)
       
   149 
       
   150     def makelock(self, info, path):
       
   151         return util.makelock(info, self.join(path))
       
   152 
       
   153     def mkdir(self, path=None):
       
   154         return os.mkdir(self.join(path))
       
   155 
       
   156     def mkstemp(self, suffix='', prefix='tmp', dir=None, text=False):
       
   157         fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
       
   158                                     dir=self.join(dir), text=text)
       
   159         dname, fname = util.split(name)
       
   160         if dir:
       
   161             return fd, os.path.join(dir, fname)
       
   162         else:
       
   163             return fd, fname
       
   164 
       
   165     def readdir(self, path=None, stat=None, skip=None):
       
   166         return osutil.listdir(self.join(path), stat, skip)
       
   167 
       
   168     def readlock(self, path):
       
   169         return util.readlock(self.join(path))
       
   170 
       
   171     def rename(self, src, dst, checkambig=False):
       
   172         """Rename from src to dst
       
   173 
       
   174         checkambig argument is used with util.filestat, and is useful
       
   175         only if destination file is guarded by any lock
       
   176         (e.g. repo.lock or repo.wlock).
       
   177         """
       
   178         dstpath = self.join(dst)
       
   179         oldstat = checkambig and util.filestat(dstpath)
       
   180         if oldstat and oldstat.stat:
       
   181             ret = util.rename(self.join(src), dstpath)
       
   182             newstat = util.filestat(dstpath)
       
   183             if newstat.isambig(oldstat):
       
   184                 # stat of renamed file is ambiguous to original one
       
   185                 newstat.avoidambig(dstpath, oldstat)
       
   186             return ret
       
   187         return util.rename(self.join(src), dstpath)
       
   188 
       
   189     def readlink(self, path):
       
   190         return os.readlink(self.join(path))
       
   191 
       
   192     def removedirs(self, path=None):
       
   193         """Remove a leaf directory and all empty intermediate ones
       
   194         """
       
   195         return util.removedirs(self.join(path))
       
   196 
       
   197     def rmtree(self, path=None, ignore_errors=False, forcibly=False):
       
   198         """Remove a directory tree recursively
       
   199 
       
   200         If ``forcibly``, this tries to remove READ-ONLY files, too.
       
   201         """
       
   202         if forcibly:
       
   203             def onerror(function, path, excinfo):
       
   204                 if function is not os.remove:
       
   205                     raise
       
   206                 # read-only files cannot be unlinked under Windows
       
   207                 s = os.stat(path)
       
   208                 if (s.st_mode & stat.S_IWRITE) != 0:
       
   209                     raise
       
   210                 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
       
   211                 os.remove(path)
       
   212         else:
       
   213             onerror = None
       
   214         return shutil.rmtree(self.join(path),
       
   215                              ignore_errors=ignore_errors, onerror=onerror)
       
   216 
       
   217     def setflags(self, path, l, x):
       
   218         return util.setflags(self.join(path), l, x)
       
   219 
       
   220     def stat(self, path=None):
       
   221         return os.stat(self.join(path))
       
   222 
       
   223     def unlink(self, path=None):
       
   224         return util.unlink(self.join(path))
       
   225 
       
   226     def unlinkpath(self, path=None, ignoremissing=False):
       
   227         return util.unlinkpath(self.join(path), ignoremissing)
       
   228 
       
   229     def utime(self, path=None, t=None):
       
   230         return os.utime(self.join(path), t)
       
   231 
       
   232     def walk(self, path=None, onerror=None):
       
   233         """Yield (dirpath, dirs, files) tuple for each directories under path
       
   234 
       
   235         ``dirpath`` is relative one from the root of this vfs. This
       
   236         uses ``os.sep`` as path separator, even you specify POSIX
       
   237         style ``path``.
       
   238 
       
   239         "The root of this vfs" is represented as empty ``dirpath``.
       
   240         """
       
   241         root = os.path.normpath(self.join(None))
       
   242         # when dirpath == root, dirpath[prefixlen:] becomes empty
       
   243         # because len(dirpath) < prefixlen.
       
   244         prefixlen = len(pathutil.normasprefix(root))
       
   245         for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
       
   246             yield (dirpath[prefixlen:], dirs, files)
       
   247 
       
   248     @contextlib.contextmanager
       
   249     def backgroundclosing(self, ui, expectedcount=-1):
       
   250         """Allow files to be closed asynchronously.
       
   251 
       
   252         When this context manager is active, ``backgroundclose`` can be passed
       
   253         to ``__call__``/``open`` to result in the file possibly being closed
       
   254         asynchronously, on a background thread.
       
   255         """
       
   256         # This is an arbitrary restriction and could be changed if we ever
       
   257         # have a use case.
       
   258         vfs = getattr(self, 'vfs', self)
       
   259         if getattr(vfs, '_backgroundfilecloser', None):
       
   260             raise error.Abort(
       
   261                 _('can only have 1 active background file closer'))
       
   262 
       
   263         with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
       
   264             try:
       
   265                 vfs._backgroundfilecloser = bfc
       
   266                 yield bfc
       
   267             finally:
       
   268                 vfs._backgroundfilecloser = None
       
   269 
       
   270 class vfs(abstractvfs):
       
   271     '''Operate files relative to a base directory
       
   272 
       
   273     This class is used to hide the details of COW semantics and
       
   274     remote file access from higher level code.
       
   275     '''
       
   276     def __init__(self, base, audit=True, expandpath=False, realpath=False):
       
   277         if expandpath:
       
   278             base = util.expandpath(base)
       
   279         if realpath:
       
   280             base = os.path.realpath(base)
       
   281         self.base = base
       
   282         self.mustaudit = audit
       
   283         self.createmode = None
       
   284         self._trustnlink = None
       
   285 
       
   286     @property
       
   287     def mustaudit(self):
       
   288         return self._audit
       
   289 
       
   290     @mustaudit.setter
       
   291     def mustaudit(self, onoff):
       
   292         self._audit = onoff
       
   293         if onoff:
       
   294             self.audit = pathutil.pathauditor(self.base)
       
   295         else:
       
   296             self.audit = util.always
       
   297 
       
   298     @util.propertycache
       
   299     def _cansymlink(self):
       
   300         return util.checklink(self.base)
       
   301 
       
   302     @util.propertycache
       
   303     def _chmod(self):
       
   304         return util.checkexec(self.base)
       
   305 
       
   306     def _fixfilemode(self, name):
       
   307         if self.createmode is None or not self._chmod:
       
   308             return
       
   309         os.chmod(name, self.createmode & 0o666)
       
   310 
       
   311     def __call__(self, path, mode="r", text=False, atomictemp=False,
       
   312                  notindexed=False, backgroundclose=False, checkambig=False):
       
   313         '''Open ``path`` file, which is relative to vfs root.
       
   314 
       
   315         Newly created directories are marked as "not to be indexed by
       
   316         the content indexing service", if ``notindexed`` is specified
       
   317         for "write" mode access.
       
   318 
       
   319         If ``backgroundclose`` is passed, the file may be closed asynchronously.
       
   320         It can only be used if the ``self.backgroundclosing()`` context manager
       
   321         is active. This should only be specified if the following criteria hold:
       
   322 
       
   323         1. There is a potential for writing thousands of files. Unless you
       
   324            are writing thousands of files, the performance benefits of
       
   325            asynchronously closing files is not realized.
       
   326         2. Files are opened exactly once for the ``backgroundclosing``
       
   327            active duration and are therefore free of race conditions between
       
   328            closing a file on a background thread and reopening it. (If the
       
   329            file were opened multiple times, there could be unflushed data
       
   330            because the original file handle hasn't been flushed/closed yet.)
       
   331 
       
   332         ``checkambig`` argument is passed to atomictemplfile (valid
       
   333         only for writing), and is useful only if target file is
       
   334         guarded by any lock (e.g. repo.lock or repo.wlock).
       
   335         '''
       
   336         if self._audit:
       
   337             r = util.checkosfilename(path)
       
   338             if r:
       
   339                 raise error.Abort("%s: %r" % (r, path))
       
   340         self.audit(path)
       
   341         f = self.join(path)
       
   342 
       
   343         if not text and "b" not in mode:
       
   344             mode += "b" # for that other OS
       
   345 
       
   346         nlink = -1
       
   347         if mode not in ('r', 'rb'):
       
   348             dirname, basename = util.split(f)
       
   349             # If basename is empty, then the path is malformed because it points
       
   350             # to a directory. Let the posixfile() call below raise IOError.
       
   351             if basename:
       
   352                 if atomictemp:
       
   353                     util.makedirs(dirname, self.createmode, notindexed)
       
   354                     return util.atomictempfile(f, mode, self.createmode,
       
   355                                                checkambig=checkambig)
       
   356                 try:
       
   357                     if 'w' in mode:
       
   358                         util.unlink(f)
       
   359                         nlink = 0
       
   360                     else:
       
   361                         # nlinks() may behave differently for files on Windows
       
   362                         # shares if the file is open.
       
   363                         with util.posixfile(f):
       
   364                             nlink = util.nlinks(f)
       
   365                             if nlink < 1:
       
   366                                 nlink = 2 # force mktempcopy (issue1922)
       
   367                 except (OSError, IOError) as e:
       
   368                     if e.errno != errno.ENOENT:
       
   369                         raise
       
   370                     nlink = 0
       
   371                     util.makedirs(dirname, self.createmode, notindexed)
       
   372                 if nlink > 0:
       
   373                     if self._trustnlink is None:
       
   374                         self._trustnlink = nlink > 1 or util.checknlink(f)
       
   375                     if nlink > 1 or not self._trustnlink:
       
   376                         util.rename(util.mktempcopy(f), f)
       
   377         fp = util.posixfile(f, mode)
       
   378         if nlink == 0:
       
   379             self._fixfilemode(f)
       
   380 
       
   381         if checkambig:
       
   382             if mode in ('r', 'rb'):
       
   383                 raise error.Abort(_('implementation error: mode %s is not'
       
   384                                     ' valid for checkambig=True') % mode)
       
   385             fp = checkambigatclosing(fp)
       
   386 
       
   387         if backgroundclose:
       
   388             if not self._backgroundfilecloser:
       
   389                 raise error.Abort(_('backgroundclose can only be used when a '
       
   390                                   'backgroundclosing context manager is active')
       
   391                                   )
       
   392 
       
   393             fp = delayclosedfile(fp, self._backgroundfilecloser)
       
   394 
       
   395         return fp
       
   396 
       
   397     def symlink(self, src, dst):
       
   398         self.audit(dst)
       
   399         linkname = self.join(dst)
       
   400         try:
       
   401             os.unlink(linkname)
       
   402         except OSError:
       
   403             pass
       
   404 
       
   405         util.makedirs(os.path.dirname(linkname), self.createmode)
       
   406 
       
   407         if self._cansymlink:
       
   408             try:
       
   409                 os.symlink(src, linkname)
       
   410             except OSError as err:
       
   411                 raise OSError(err.errno, _('could not symlink to %r: %s') %
       
   412                               (src, err.strerror), linkname)
       
   413         else:
       
   414             self.write(dst, src)
       
   415 
       
   416     def join(self, path, *insidef):
       
   417         if path:
       
   418             return os.path.join(self.base, path, *insidef)
       
   419         else:
       
   420             return self.base
       
   421 
       
   422 opener = vfs
       
   423 
       
   424 class auditvfs(object):
       
   425     def __init__(self, vfs):
       
   426         self.vfs = vfs
       
   427 
       
   428     @property
       
   429     def mustaudit(self):
       
   430         return self.vfs.mustaudit
       
   431 
       
   432     @mustaudit.setter
       
   433     def mustaudit(self, onoff):
       
   434         self.vfs.mustaudit = onoff
       
   435 
       
   436     @property
       
   437     def options(self):
       
   438         return self.vfs.options
       
   439 
       
   440     @options.setter
       
   441     def options(self, value):
       
   442         self.vfs.options = value
       
   443 
       
   444 class filtervfs(abstractvfs, auditvfs):
       
   445     '''Wrapper vfs for filtering filenames with a function.'''
       
   446 
       
   447     def __init__(self, vfs, filter):
       
   448         auditvfs.__init__(self, vfs)
       
   449         self._filter = filter
       
   450 
       
   451     def __call__(self, path, *args, **kwargs):
       
   452         return self.vfs(self._filter(path), *args, **kwargs)
       
   453 
       
   454     def join(self, path, *insidef):
       
   455         if path:
       
   456             return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
       
   457         else:
       
   458             return self.vfs.join(path)
       
   459 
       
   460 filteropener = filtervfs
       
   461 
       
   462 class readonlyvfs(abstractvfs, auditvfs):
       
   463     '''Wrapper vfs preventing any writing.'''
       
   464 
       
   465     def __init__(self, vfs):
       
   466         auditvfs.__init__(self, vfs)
       
   467 
       
   468     def __call__(self, path, mode='r', *args, **kw):
       
   469         if mode not in ('r', 'rb'):
       
   470             raise error.Abort(_('this vfs is read only'))
       
   471         return self.vfs(path, mode, *args, **kw)
       
   472 
       
   473     def join(self, path, *insidef):
       
   474         return self.vfs.join(path, *insidef)
       
   475 
       
   476 class closewrapbase(object):
       
   477     """Base class of wrapper, which hooks closing
       
   478 
       
   479     Do not instantiate outside of the vfs layer.
       
   480     """
       
   481     def __init__(self, fh):
       
   482         object.__setattr__(self, '_origfh', fh)
       
   483 
       
   484     def __getattr__(self, attr):
       
   485         return getattr(self._origfh, attr)
       
   486 
       
   487     def __setattr__(self, attr, value):
       
   488         return setattr(self._origfh, attr, value)
       
   489 
       
   490     def __delattr__(self, attr):
       
   491         return delattr(self._origfh, attr)
       
   492 
       
   493     def __enter__(self):
       
   494         return self._origfh.__enter__()
       
   495 
       
   496     def __exit__(self, exc_type, exc_value, exc_tb):
       
   497         raise NotImplementedError('attempted instantiating ' + str(type(self)))
       
   498 
       
   499     def close(self):
       
   500         raise NotImplementedError('attempted instantiating ' + str(type(self)))
       
   501 
       
   502 class delayclosedfile(closewrapbase):
       
   503     """Proxy for a file object whose close is delayed.
       
   504 
       
   505     Do not instantiate outside of the vfs layer.
       
   506     """
       
   507     def __init__(self, fh, closer):
       
   508         super(delayclosedfile, self).__init__(fh)
       
   509         object.__setattr__(self, '_closer', closer)
       
   510 
       
   511     def __exit__(self, exc_type, exc_value, exc_tb):
       
   512         self._closer.close(self._origfh)
       
   513 
       
   514     def close(self):
       
   515         self._closer.close(self._origfh)
       
   516 
       
   517 class backgroundfilecloser(object):
       
   518     """Coordinates background closing of file handles on multiple threads."""
       
   519     def __init__(self, ui, expectedcount=-1):
       
   520         self._running = False
       
   521         self._entered = False
       
   522         self._threads = []
       
   523         self._threadexception = None
       
   524 
       
   525         # Only Windows/NTFS has slow file closing. So only enable by default
       
   526         # on that platform. But allow to be enabled elsewhere for testing.
       
   527         defaultenabled = pycompat.osname == 'nt'
       
   528         enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
       
   529 
       
   530         if not enabled:
       
   531             return
       
   532 
       
   533         # There is overhead to starting and stopping the background threads.
       
   534         # Don't do background processing unless the file count is large enough
       
   535         # to justify it.
       
   536         minfilecount = ui.configint('worker', 'backgroundcloseminfilecount',
       
   537                                     2048)
       
   538         # FUTURE dynamically start background threads after minfilecount closes.
       
   539         # (We don't currently have any callers that don't know their file count)
       
   540         if expectedcount > 0 and expectedcount < minfilecount:
       
   541             return
       
   542 
       
   543         # Windows defaults to a limit of 512 open files. A buffer of 128
       
   544         # should give us enough headway.
       
   545         maxqueue = ui.configint('worker', 'backgroundclosemaxqueue', 384)
       
   546         threadcount = ui.configint('worker', 'backgroundclosethreadcount', 4)
       
   547 
       
   548         ui.debug('starting %d threads for background file closing\n' %
       
   549                  threadcount)
       
   550 
       
   551         self._queue = util.queue(maxsize=maxqueue)
       
   552         self._running = True
       
   553 
       
   554         for i in range(threadcount):
       
   555             t = threading.Thread(target=self._worker, name='backgroundcloser')
       
   556             self._threads.append(t)
       
   557             t.start()
       
   558 
       
   559     def __enter__(self):
       
   560         self._entered = True
       
   561         return self
       
   562 
       
   563     def __exit__(self, exc_type, exc_value, exc_tb):
       
   564         self._running = False
       
   565 
       
   566         # Wait for threads to finish closing so open files don't linger for
       
   567         # longer than lifetime of context manager.
       
   568         for t in self._threads:
       
   569             t.join()
       
   570 
       
   571     def _worker(self):
       
   572         """Main routine for worker thread."""
       
   573         while True:
       
   574             try:
       
   575                 fh = self._queue.get(block=True, timeout=0.100)
       
   576                 # Need to catch or the thread will terminate and
       
   577                 # we could orphan file descriptors.
       
   578                 try:
       
   579                     fh.close()
       
   580                 except Exception as e:
       
   581                     # Stash so can re-raise from main thread later.
       
   582                     self._threadexception = e
       
   583             except util.empty:
       
   584                 if not self._running:
       
   585                     break
       
   586 
       
   587     def close(self, fh):
       
   588         """Schedule a file for closing."""
       
   589         if not self._entered:
       
   590             raise error.Abort(_('can only call close() when context manager '
       
   591                               'active'))
       
   592 
       
   593         # If a background thread encountered an exception, raise now so we fail
       
   594         # fast. Otherwise we may potentially go on for minutes until the error
       
   595         # is acted on.
       
   596         if self._threadexception:
       
   597             e = self._threadexception
       
   598             self._threadexception = None
       
   599             raise e
       
   600 
       
   601         # If we're not actively running, close synchronously.
       
   602         if not self._running:
       
   603             fh.close()
       
   604             return
       
   605 
       
   606         self._queue.put(fh, block=True, timeout=None)
       
   607 
       
   608 class checkambigatclosing(closewrapbase):
       
   609     """Proxy for a file object, to avoid ambiguity of file stat
       
   610 
       
   611     See also util.filestat for detail about "ambiguity of file stat".
       
   612 
       
   613     This proxy is useful only if the target file is guarded by any
       
   614     lock (e.g. repo.lock or repo.wlock)
       
   615 
       
   616     Do not instantiate outside of the vfs layer.
       
   617     """
       
   618     def __init__(self, fh):
       
   619         super(checkambigatclosing, self).__init__(fh)
       
   620         object.__setattr__(self, '_oldstat', util.filestat(fh.name))
       
   621 
       
   622     def _checkambig(self):
       
   623         oldstat = self._oldstat
       
   624         if oldstat.stat:
       
   625             newstat = util.filestat(self._origfh.name)
       
   626             if newstat.isambig(oldstat):
       
   627                 # stat of changed file is ambiguous to original one
       
   628                 newstat.avoidambig(self._origfh.name, oldstat)
       
   629 
       
   630     def __exit__(self, exc_type, exc_value, exc_tb):
       
   631         self._origfh.__exit__(exc_type, exc_value, exc_tb)
       
   632         self._checkambig()
       
   633 
       
   634     def close(self):
       
   635         self._origfh.close()
       
   636         self._checkambig()