diff --git a/mercurial/mergestate.py b/mercurial/mergestate.py --- a/mercurial/mergestate.py +++ b/mercurial/mergestate.py @@ -91,7 +91,40 @@ } -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 @@ -160,11 +193,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 @@ -176,7 +210,6 @@ if node: self._local = node self._other = other - self._readmergedriver = None if self.mergedriver: self._mdstate = MERGE_DRIVER_STATE_SUCCESS else: @@ -361,30 +394,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" @@ -857,3 +866,81 @@ 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() + + def add(self, fcl, fco, fca, fd): + """add a new (potentially?) conflicting file to the merge state""" + self._conflicts.add(fcl.path()) + + # 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: + self._ancestor_ctx = next(self._repo.set(b'ancestor(%ln)', (node, other))) + 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 + 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