[Updated] [+++- ] D8567: mergestate: implement trivial in-memory mergestate

durin42 (Augie Fackler) phabricator at mercurial-scm.org
Thu Jun 11 16:08:10 UTC 2020


durin42 updated this revision to Diff 21611.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D8567?vs=21544&id=21611

BRANCH
  default

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D8567/new/

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

AFFECTED FILES
  mercurial/mergestate.py

CHANGE DETAILS

diff --git a/mercurial/mergestate.py b/mercurial/mergestate.py
--- a/mercurial/mergestate.py
+++ b/mercurial/mergestate.py
@@ -86,7 +86,40 @@
 ACTION_GET_OTHER_AND_STORE = b'gs'
 
 
-class mergestate(object):
+class _basemergestate(object):
+    def __init__(self, repo):
+        self._repo = repo
+        self._readmergedriver = None
+
+    @util.propertycache
+    def mergedriver(self):
+        # protect against the following:
+        # - A configures a malicious merge driver in their hgrc, then
+        #   pauses the merge
+        # - A edits their hgrc to remove references to the merge driver
+        # - A gives a copy of their entire repo, including .hg, to B
+        # - B inspects .hgrc and finds it to be clean
+        # - B then continues the merge and the malicious merge driver
+        #  gets invoked
+        configmergedriver = self._repo.ui.config(
+            b'experimental', b'mergedriver'
+        )
+        if (
+            self._readmergedriver is not None
+            and self._readmergedriver != configmergedriver
+        ):
+            raise error.ConfigError(
+                _(b"merge driver changed since merge started"),
+                hint=_(b"revert merge driver change or abort merge"),
+            )
+
+        return configmergedriver
+
+    def reset(self, node=None, other=None, labels=None):
+        self._readmergedriver = None
+
+
+class mergestate(_basemergestate):
     '''track 3-way merge state of individual files
 
     The merge state is stored on disk when needed. Two files are used: one with
@@ -155,11 +188,12 @@
         """Initialize the merge state.
 
         Do not use this directly! Instead call read() or clean()."""
-        self._repo = repo
+        super(mergestate, self).__init__(repo)
         self._dirty = False
         self._labels = None
 
     def reset(self, node=None, other=None, labels=None):
+        super(mergestate, self).reset(node=node, other=other, labels=labels)
         self._state = {}
         self._stateextras = {}
         self._local = None
@@ -171,7 +205,6 @@
         if node:
             self._local = node
             self._other = other
-        self._readmergedriver = None
         if self.mergedriver:
             self._mdstate = MERGE_DRIVER_STATE_SUCCESS
         else:
@@ -356,30 +389,6 @@
         return records
 
     @util.propertycache
-    def mergedriver(self):
-        # protect against the following:
-        # - A configures a malicious merge driver in their hgrc, then
-        #   pauses the merge
-        # - A edits their hgrc to remove references to the merge driver
-        # - A gives a copy of their entire repo, including .hg, to B
-        # - B inspects .hgrc and finds it to be clean
-        # - B then continues the merge and the malicious merge driver
-        #  gets invoked
-        configmergedriver = self._repo.ui.config(
-            b'experimental', b'mergedriver'
-        )
-        if (
-            self._readmergedriver is not None
-            and self._readmergedriver != configmergedriver
-        ):
-            raise error.ConfigError(
-                _(b"merge driver changed since merge started"),
-                hint=_(b"revert merge driver change or abort merge"),
-            )
-
-        return configmergedriver
-
-    @util.propertycache
     def local(self):
         if self._local is None:
             msg = b"local accessed but self._local isn't set"
@@ -848,3 +857,105 @@
             repo.dirstate.copy(f0, f)
         else:
             repo.dirstate.normal(f)
+
+
+class memmergestate(_basemergestate):
+    def __init__(self, repo, ctx):
+        super(memmergestate, self).__init__(repo)
+        self._basectx = ctx
+        self.reset()
+        self._ancestor_filectxs = {}
+
+    def add(self, fcl, fco, fca, fd):
+        """add a new (potentially?) conflicting file to the merge state"""
+        self._conflicts.add(fcl.path())
+        # TODO(augie): the rebase codepath depends on non-implicit
+        # ancestor. I think we should fix things so that ancestor can
+        # be passed in to reset().
+        self._ancestor_filectxs[fcl.path()] = fca
+
+    # Since memmergestate isn't mutable yet, these are all trivial
+    # implementations used by the "happy path" in merge code.
+    def reset(self, node=None, other=None, labels=None):
+        super(memmergestate, self).reset(node=node, other=other, labels=labels)
+        self._local = node
+        self._other = other
+        if self._local is not None:
+            try:
+                self._ancestor_ctx = next(
+                    self._repo.set(b'ancestor(%ln)', (node, other))
+                )
+            except StopIteration:
+                # This will almost certainly be an issue, but if
+                # callers consistently pass a filectx in add() when we
+                # can't figure out the ancestor this won't matter. It
+                # would be a better API for reset() to take an
+                # ancestor ctx, but that's a broader cleanup.
+                self._ancestor_ctx = None
+        self._labels = labels
+        self._conflicts = set()
+
+    def commit(self):
+        if self._conflicts:
+            error.InMemoryMergeConflictsError(
+                'cannot commit memmergestate with conflicts, have %d conflicts'
+                % self.unresolvedcount()
+            )
+
+    def counts(self):
+        return 0, 0, 0
+
+    def unresolvedcount(self):
+        return len(self._conflicts)
+
+    def actions(self):
+        return {}
+
+    @property
+    def mergedriver(self):
+        md = super(memmergestate, self).mergedriver
+        if md:
+            raise error.InMemoryMergeConflictsError(
+                b"in-memory merge does not support mergedriver"
+            )
+        return md
+
+    def addmergedother(self, path):
+        # I'm very dubious this is right.
+        pass
+
+    def preresolve(self, dfile, wctx):
+        return self._resolve(True, dfile, wctx)
+
+    def resolve(self, dfile, wctx):
+        return self._resolve(False, dfile, wctx)[1]
+
+    def _resolve(self, preresolve, dfile, wctx):
+        # TODO: clean up _filectxorabsent API?
+        fcd = _filectxorabsent(
+            nullhex if dfile not in wctx else None, wctx, dfile
+        )
+        octx = self._repo[self._other]
+        fco = _filectxorabsent(
+            nullhex if dfile not in octx else None, octx, dfile
+        )
+        actx = self._ancestor_ctx
+        if dfile in self._ancestor_filectxs:
+            fca = self._ancestor_filectxs[dfile]
+        else:
+            fca = _filectxorabsent(
+                nullhex if dfile not in actx else None, actx, dfile
+            )
+        fn = filemerge.premerge if preresolve else filemerge.filemerge
+        complete, mergeret, deleted = fn(
+            self._repo,
+            wctx,
+            self._local,
+            dfile,  # orig
+            fcd,
+            fco,
+            fca,
+            labels=self._labels,
+        )
+        del deleted  # unused
+        return complete, mergeret



To: durin42, #hg-reviewers
Cc: martinvonz, mercurial-patches
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.mercurial-scm.org/pipermail/mercurial-patches/attachments/20200611/93a0b26e/attachment-0002.html>


More information about the Mercurial-patches mailing list