D616: context: add overlayworkingcontext and overlayworkingfilectx

phillco (Phil Cohen) phabricator at mercurial-scm.org
Sun Sep 3 19:41:09 UTC 2017


phillco created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  These two classes will be used extensively in the first in-memory merge
  milestone.

REPOSITORY
  rHG Mercurial

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

AFFECTED FILES
  mercurial/context.py

CHANGE DETAILS

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1974,6 +1974,186 @@
     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, either `data` or `flags` must be non-None (either both,
+    or just `flags`), 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._underlyingctx(path).data()
+            else:
+                raise IOError("No such file or directory: %s" % self._path)
+        else:
+            return self._underlyingctx(path).data()
+
+    def filedate(self, path):
+        if self.isdirty(path):
+            return self._cache[path]['date']
+        else:
+            return self._underlyingctx(path).date()
+
+    def flags(self, path):
+        if self.isdirty(path):
+            if self._cache[path]['exists']:
+                return self._cache[path]['flags']
+            else:
+                raise IOError("No such file or directory: %s" % self._path)
+        else:
+            return self._underlyingctx(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
+        pass
+
+    def setflags(self, path, l, x):
+        self._markdirty(path)
+        self._cache[path]['exists'] = True
+        self._cache[path]['date'] = util.makedate()
+        self._cache[path]['flags'] = (l and 'l' or '') + (x and 'x' or '')
+
+    def remove(self, path):
+        self._markdirty(path)
+        self._cache[path]['exists'] = False
+        self._cache[path]['data'] = None
+        self._cache[path]['flags'] = None
+
+    def exists(self, path):
+        if self.isdirty(path):
+            return self._cache[path]['exists']
+        return self._underlyingctx(path).exists()
+
+    def size(self, path):
+        if self.isdirty(path):
+            if self._cache[path]['exists']:
+                return len(self._cache[path]['data'])
+            else:
+                raise IOError("No such file or directory: %s" % self._path)
+        return self._underlyingctx(path).size()
+
+    def flushall(self):
+        for path in self._writeorder:
+            if self._cache[path]['exists'] is True:
+                self._underlyingctx(path).clearunknown()
+                if self._cache[path]['data'] is not None:
+                    if self._cache[path]['flags'] is None:
+                        raise error.ProgrammingError('data set but not flags')
+                    self._underlyingctx(path).write(
+                        self._cache[path]['data'],
+                        self._cache[path]['flags'])
+                else:
+                    self._underlyingctx(path).setflags(
+                        'l' in self._cache[path]['flags'],
+                        'x' in self._cache[path]['flags'])
+            elif self._cache[path]['exists'] is False:
+                self._underlyingctx(path).remove(path)
+            else:
+                continue
+        self.clean()
+
+    def isdirty(self, path):
+        return path in self._cache
+
+    def clean(self):
+        self._cache = {}
+        self._writeorder = []
+
+    def _markdirty(self, path):
+        if path not in self._cache:
+            self._cache[path] = {
+                'exists': None,
+                'data': None,
+                'flags': None,
+            }
+            self._writeorder.append(path)
+
+    def filectx(self, path, filelog=None):
+        return overlayworkingfilectx(self._repo, path, parent=self,
+                                     filelog=filelog)
+
+    def _underlyingctx(self, path):
+        return self._wrappedctx.filectx(path)
+
+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.



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


More information about the Mercurial-devel mailing list