diff --git a/mercurial/mergestate.py b/mercurial/mergestate.py --- a/mercurial/mergestate.py +++ b/mercurial/mergestate.py @@ -85,7 +85,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 @@ -154,11 +187,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 @@ -170,7 +204,6 @@ if node: self._local = node self._other = other - self._readmergedriver = None if self.mergedriver: self._mdstate = MERGE_DRIVER_STATE_SUCCESS else: @@ -355,30 +388,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,35 @@ repo.dirstate.copy(f0, f) else: repo.dirstate.normal(f) + + +class memmergestate(_basemergestate): + def __init__(self, repo): + super(memmergestate, self).__init__(repo) + 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._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 {}