Mercurial > hg
changeset 26649:f618b6aa8cdd
merge.mergestate: add support for persisting a custom merge driver
A 'merge driver' is a coordinator for the overall merge process. It will be
able to control:
- tools for individual files, much like the merge-patterns configuration does
today
- tools that can work across groups of files
- the ordering of file resolution
- resolution of automatically generated files
- adding and removing additional files to and from the dirstate
Since it is a critical part of the merge process, it really is part of the
merge state.
This is a lowercase character (i.e. optional) because ignoring this is fine for
older versions of Mercurial -- however, if there are any files that are
specially treated by the driver, we should abort. That will happen in upcoming
patches.
There is a potential security issue with storing the merge driver in the merge
state. See the inline comments for more details.
author | Siddharth Agarwal <sid0@fb.com> |
---|---|
date | Wed, 30 Sep 2015 21:42:52 -0700 |
parents | c347d532bb56 |
children | 6ff5534c8afc |
files | mercurial/merge.py |
diffstat | 1 files changed, 37 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/merge.py Tue Oct 13 12:30:39 2015 -0700 +++ b/mercurial/merge.py Wed Sep 30 21:42:52 2015 -0700 @@ -61,6 +61,14 @@ L: the node of the "local" part of the merge (hexified version) O: the node of the "other" part of the merge (hexified version) F: a file to be merged entry + m: the external merge driver defined for this merge plus its run state + (experimental) + + Merge driver run states (experimental): + u: driver-resolved files unmarked -- needs to be run next time we're about + to resolve or commit + m: driver-resolved files marked -- only needs to be run before commit + s: success/skipped -- does not need to be run any more ''' statepathv1 = 'merge/state' statepathv2 = 'merge/state2' @@ -77,6 +85,7 @@ if node: self._local = node self._other = other + self._mdstate = 'u' shutil.rmtree(self._repo.join('merge'), True) self._dirty = False @@ -89,12 +98,33 @@ self._state = {} self._local = None self._other = None + self._mdstate = 'u' records = self._readrecords() for rtype, record in records: if rtype == 'L': self._local = bin(record) elif rtype == 'O': self._other = bin(record) + elif rtype == 'm': + bits = record.split('\0', 1) + mdstate = bits[1] + if len(mdstate) != 1 or mdstate not in 'ums': + # the merge driver should be idempotent, so just rerun it + mdstate = 'u' + + # 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 + if self.mergedriver != bits[0]: + raise error.ConfigError( + _("merge driver changed since merge started"), + hint=_("revert merge driver change or abort merge")) + self._mdstate = mdstate elif rtype == 'F': bits = record.split('\0') self._state[bits[0]] = bits[1:] @@ -198,6 +228,10 @@ raise return records + @util.propertycache + def mergedriver(self): + return self._repo.ui.config('experimental', 'mergedriver') + def active(self): """Whether mergestate is active. @@ -216,6 +250,9 @@ records = [] records.append(('L', hex(self._local))) records.append(('O', hex(self._other))) + if self.mergedriver: + records.append(('m', '\0'.join([ + self.mergedriver, self._mdstate]))) for d, v in self._state.iteritems(): records.append(('F', '\0'.join([d] + v))) self._writerecords(records)