--- a/mercurial/context.py Sun Sep 10 18:52:40 2017 -0700
+++ b/mercurial/context.py Mon Sep 11 13:03:27 2017 -0700
@@ -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,191 @@
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 error.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 error.ProgrammingError("No such file or directory: %s" %
+ self._path)
+ else:
+ return self._wrappedctx[path].flags()
+
+ def write(self, path, data, flags=''):
+ if data is None:
+ raise error.ProgrammingError("data must be non-None")
+ self._markdirty(path, exists=True, data=data, date=util.makedate(),
+ 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, exists=False)
+
+ 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'].strip())
+ 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 error.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, exists, data=None, date=None, flags=''):
+ if path not in self._cache:
+ self._writeorder.append(path)
+
+ self._cache[path] = {
+ 'exists': exists,
+ 'data': data,
+ 'date': date,
+ '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.