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