Patchwork D616: context: add overlayworkingcontext and overlayworkingfilectx

login
register
mail settings
Submitter phabricator
Date Sept. 11, 2017, 5:23 a.m.
Message ID <1f40724ba88b95834955fb03e718b32a@localhost.localdomain>
Download mbox | patch
Permalink /patch/23764/
State Not Applicable
Headers show

Comments

phabricator - Sept. 11, 2017, 5:23 a.m.
phillco updated this revision to Diff 1705.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D616?vs=1699&id=1705

REVISION DETAIL
  https://phab.mercurial-scm.org/D616

AFFECTED FILES
  mercurial/context.py

CHANGE DETAILS




To: phillco, #hg-reviewers
Cc: quark, martinvonz, mercurial-devel

Patch

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1661,6 +1661,9 @@ 
                               listsubrepos=listsubrepos, badfn=badfn,
                               icasefs=icasefs)
 
+    def flushall(self):
+        pass # For overlayworkingfilectx compatibility.
+
     def _filtersuspectsymlink(self, files):
         if not files or self._repo.dirstate._checklink:
             return files
@@ -1974,6 +1977,198 @@ 
     def setflags(self, l, x):
         self._repo.wvfs.setflags(self._path, l, x)
 
+class overlayworkingctx(workingctx):
+    """Wraps another mutable context with a write-back cache that can be flushed
+    at a later time.
+
+    self._cache[path] maps to a dict with keys: {
+        'exists': bool?
+        'date': date?
+        'data': str?
+        'flags': str?
+    }
+    If `exists` is True, `flags` must be non-None and 'date' is non-None. If it
+    is `False`, the file was deleted.
+    """
+
+    def __init__(self, repo, wrappedctx):
+        super(overlayworkingctx, self).__init__(repo)
+        self._repo = repo
+        self._wrappedctx = wrappedctx
+        self._clean()
+
+    def data(self, path):
+        if self.isdirty(path):
+            if self._cache[path]['exists']:
+                if self._cache[path]['data']:
+                    return self._cache[path]['data']
+                else:
+                    # Must fallback here, too, because we only set flags.
+                    return self._wrappedctx[path].data()
+            else:
+                raise ProgrammingError("No such file or directory: %s" %
+                                       self._path)
+        else:
+            return self._wrappedctx[path].data()
+
+    def filedate(self, path):
+        if self.isdirty(path):
+            return self._cache[path]['date']
+        else:
+            return self._wrappedctx[path].date()
+
+    def flags(self, path):
+        if self.isdirty(path):
+            if self._cache[path]['exists']:
+                return self._cache[path]['flags']
+            else:
+                raise ProgrammingError("No such file or directory: %s" %
+                                       self._path)
+        else:
+            return self._wrappedctx[path].flags()
+
+    def write(self, path, data, flags=None):
+        if data is None:
+            raise error.ProgrammingError("data must be non-None")
+        self._markdirty(path)
+        self._cache[path]['exists'] = True
+        self._cache[path]['data'] = data
+        self._cache[path]['date'] = util.makedate()
+        if flags is not None:
+            self._cache[path]['flags'] = flags
+
+    def setflags(self, path, l, x):
+        self._markdirty(path, exists=True, date=util.makedate(),
+                        flags=(l and 'l' or '') + (x and 'x' or ''))
+
+    def remove(self, path):
+        self._markdirty(path)
+
+    def exists(self, path):
+        """exists behaves like `lexists`, but needs to follow symlinks and
+        return False if they are broken.
+        """
+        if self.isdirty(path):
+            # If this path exists and is a symlink, "follow" it by calling
+            # exists on the destination path.
+            if (self._cache[path]['exists'] and
+                        'l' in self._cache[path]['flags']):
+                return self.exists(self._cache[path]['data'])
+            else:
+                return self._cache[path]['exists']
+        return self._wrappedctx[path].exists()
+
+    def lexists(self, path):
+        """lexists returns True if the path exists"""
+        if self.isdirty(path):
+            return self._cache[path]['exists']
+        return self._wrappedctx[path].lexists()
+
+    def size(self, path):
+        if self.isdirty(path):
+            if self._cache[path]['exists']:
+                return len(self._cache[path]['data'])
+            else:
+                raise ProgrammingError("No such file or directory: %s" %
+                                       self._path)
+        return self._wrappedctx[path].size()
+
+    def flushall(self):
+        for path in self._writeorder:
+            entry = self._cache[path]
+            if entry['exists']:
+                self._wrappedctx[path].clearunknown()
+                if entry['data'] is not None:
+                    if entry['flags'] is None:
+                        raise error.ProgrammingError('data set but not flags')
+                    self._wrappedctx[path].write(
+                        entry['data'],
+                        entry['flags'])
+                else:
+                    self._wrappedctx[path].setflags(
+                        'l' in entry['flags'],
+                        'x' in entry['flags'])
+            else:
+                self._wrappedctx[path].remove(path)
+        self._clean()
+
+    def isdirty(self, path):
+        return path in self._cache
+
+    def _clean(self):
+        self._cache = {}
+        self._writeorder = []
+
+    def _markdirty(self, path, flags='', exists=True, data=None, date=None):
+        if path not in self._cache:
+            self._cache[path] = {
+                'exists': exists,
+                'data': data,
+                'date': date,
+                'flags': flags,
+            }
+            self._writeorder.append(path)
+        self._cache[path]['exists'] = exists
+        self._cache[path]['data'] = data
+        self._cache[path]['date'] = date
+        self._cache[path]['flags'] = flags
+
+    def filectx(self, path, filelog=None):
+        return overlayworkingfilectx(self._repo, path, parent=self,
+                                     filelog=filelog)
+
+class overlayworkingfilectx(workingfilectx):
+    """Wrap a ``workingfilectx`` but intercepts all writes into an in-memory
+    cache, which can be flushed through later by calling ``flush()``."""
+
+    def __init__(self, repo, path, filelog=None, parent=None):
+        super(overlayworkingfilectx, self).__init__(repo, path, filelog,
+                                                    parent)
+        self._repo = repo
+        self._parent = parent
+        self._path = path
+
+    def ctx(self):
+        return self._parent
+
+    def data(self):
+        return self._parent.data(self._path)
+
+    def date(self):
+        return self._parent.filedate(self._path)
+
+    def exists(self):
+        return self.lexists()
+
+    def lexists(self):
+        return self._parent.exists(self._path)
+
+    def renamed(self):
+        # Copies are currently tracked in the dirstate as before. Straight copy
+        # from workingfilectx.
+        rp = self._repo.dirstate.copied(self._path)
+        if not rp:
+            return None
+        return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
+
+    def size(self):
+        return self._parent.size(self._path)
+
+    def audit(self):
+        pass
+
+    def flags(self):
+        return self._parent.flags(self._path)
+
+    def setflags(self, islink, isexec):
+        return self._parent.setflags(self._path, islink, isexec)
+
+    def write(self, data, flags, backgroundclose=False):
+        return self._parent.write(self._path, data, flags)
+
+    def remove(self, ignoremissing=False):
+        return self._parent.remove(self._path)
+
 class workingcommitctx(workingctx):
     """A workingcommitctx object makes access to data related to
     the revision being committed convenient.