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.