[PATCH 3 of 3 V2] memctx: allow the memlightctx thats reusing the manifest node

Jun Wu quark at fb.com
Thu Nov 17 15:11:35 EST 2016


Per discussion offline, I think this (a new class) approach is the cleanest
and safest approach to reuse manifest nodes.

"absorb" has an "overlaycontext" which takes an "original" context, but
overrides some file contents. In the future we may want to have such
overlaycontext, while the "memlightctx" is a special case where you don't
override any file contents. Note: that should not require a resend of this
patch to implement such overlaycontext feature right now.

Excerpts from Mateusz Kwapich's message of 2016-11-17 11:31:12 -0800:
> # HG changeset patch
> # User Mateusz Kwapich <mitrandir at fb.com>
> # Date 1479410909 28800
> #      Thu Nov 17 11:28:29 2016 -0800
> # Node ID 0d41689f79cf22d8761dd6af9cb5da86008afe94
> # Parent  4a0824bead3ba5980bd8528937fba5f7bb31ba9f
> memctx: allow the memlightctx thats reusing the manifest node
> 
> When we have a lot of files writing a new manifest revision can be expensive.
> This commit adds a possibility for memctx to reuse a manifest from a different
> commit. This can be beneficial for commands that are creating metadata changes
> without any actual files changed like "hg metaedit" in evolve extension.
> 
> I will send the change for evolve that leverages this once this is accepted.
> 
> diff --git a/mercurial/context.py b/mercurial/context.py
> --- a/mercurial/context.py
> +++ b/mercurial/context.py
> @@ -1975,3 +1975,99 @@ class memfilectx(committablefilectx):
>      def write(self, data, flags):
>          """wraps repo.wwrite"""
>          self._data = data
> +
> +class memlightctx(committablectx):
> +    """Like memctx but it's reusing the manifest of different commit.
> +    Intended to be used by lightweight operations that are creating
> +    metadata-only changes.
> +
> +    Revision information is supplied at initialization time.  'repo' is the
> +    current localrepo, 'ctx' is original revision which manifest we're reuisng
> +    'parents' is a sequence of two parent revisions identifiers (pass None for
> +    every missing parent), 'text' is the commit.
> +
> +    user receives the committer name and defaults to current repository
> +    username, date is the commit date in any format supported by
> +    util.parsedate() and defaults to current date, extra is a dictionary of
> +    metadata or is left empty.
> +    """
> +    def __new__(cls, repo, path, *args, **kwargs):
> +        return super(memlightctx, cls).__new__(cls, repo)
> +
> +    def __init__(self, repo, originalctx, parents, text, user=None, date=None,
> +                 extra=None, editor=False):
> +        super(memlightctx, self).__init__(repo, text, user, date, extra)
> +        self._rev = None
> +        self._node = None
> +        self._originalctx = originalctx
> +        self._manifestnode = originalctx.manifestnode()
> +        parents = [(p or nullid) for p in parents]
> +        p1, p2 = self._parents = [changectx(self._repo, p) for p in parents]
> +
> +        # sanity check to ensure that the reused manifest parents are
> +        # manifests of our commit parents
> +        mp1, mp2 = self.manifestctx().parents
> +        if p1 != nullid and p1.manifestctx().node() != mp1:
> +            raise
> +        if p2 != nullid and p2.manifestctx().node() != mp2:
> +            raise

Maybe raise RuntimeError(...) to give the user (a new programmer) some hints
about what's wrong.

> +
> +        self._files = originalctx.files()
> +        self.substate = {}
> +
> +        if extra:
> +            self._extra = extra.copy()
> +        else:
> +            self._extra = {}
> +
> +        if self._extra.get('branch', '') == '':
> +            self._extra['branch'] = 'default'
> +
> +        if editor:
> +            self._text = editor(self._repo, self, [])
> +            self._repo.savecommitmessage(self._text)
> +
> +    def manifestnode(self):
> +        return self._manifestnode
> +
> +    @propertycache
> +    def _manifestctx(self):
> +        return self._repo.manifestlog[self._manifestnode]
> +
> +    def filectx(self, path, filelog=None):
> +        return self._originalctx.filectx(path, filelog=filelog)
> +
> +    def commit(self):
> +        """commit context to the repo"""
> +        return self._repo.commitctx(self)
> +
> +    @property
> +    def _manifest(self):
> +        return self._originalctx.manifest()
> +
> +    @propertycache
> +    def _status(self):

Could we just delegate to orignalctx._status ?

> +        """Calculate exact status from ``files`` specified in the ``origctx``
> +        and parents manifests.
> +        """
> +        man1 = self.p1().manifest()
> +        p2 = self._parents[1]
> +        # "1 < len(self._parents)" can't be used for checking
> +        # existence of the 2nd parent, because "memlightctx._parents" is
> +        # explicitly initialized by the list, of which length is 2.
> +        if p2.node() != nullid:
> +            man2 = p2.manifest()
> +            managing = lambda f: f in man1 or f in man2
> +        else:
> +            managing = lambda f: f in man1
> +
> +        modified, added, removed = [], [], []
> +        for f in self._files:
> +            if not managing(f):
> +                added.append(f)
> +            elif self[f]:
> +                modified.append(f)
> +            else:
> +                removed.append(f)
> +
> +        return scmutil.status(modified, added, removed, [], [], [], [])


More information about the Mercurial-devel mailing list