Mercurial > hg > bitsyblog
Help: internals.bid-merge
Bid Merge Algorithm
Bid merge is a feature introduced in Mercurial 3.0, a merge algorithm for dealing with complicated merges.
Bid merge is controled by the 'merge.preferancestor' configuration option. The default is set to 'merge.preferancetors=*' and enable bid merge. Mercurial will perform a bid merge in the cases where a merge otherwise would emit a note: using X as ancestor of X and X message.
Problem it is solving
Mercurial's core merge algorithm is the traditional "three-way merge". This algorithm combines all the changes in two changesets relative to a common ancestor. But with complex DAGs, it is often possible to have more than one "best" common ancestor, with no easy way to distinguish between them.
For example, C and D has 2 common ancestors in the following graph:
C D |\ /| | x | |/ \| A B \ / R
Mercurial used to arbitrarily chooses the first of these, which can result in various issues:
- unexpected hard 3-way merges that would have been completely trivial if another ancestor had been used
- conflicts that have already been resolved may reappear
- changes that have been reversed can silently oscillate
One common problem is a merge which with the "right" ancestor would be trivial to resolve because only one side changed. Using another ancestor where the same lines are different, it will give an annoying 3-way merge.
Other systems like Git have attacked some of these problems with a so-called "recursive" merge strategy, that internally merges all the possible ancestors to produce a single "virtual" ancestor to merge against. This is awkward as the internal merge itself may involve conflicts (and possibly even multiple levels of recursion), which either requires choosing a conflict disposition (e.g. always choose the local version) or exposing the user to extremely confusing merge prompts for old revisions. Generating the virtual merge also potentially involves invoking filters and extensions.
Concept
(Bid merge is pretty much the same as Consensus merge.)
Bid merge is a strategy that attempts to sensibly combine the results of the multiple possible three-way merges directly without producing a virtual ancestor. The basic idea is that for each ancestor, we perform a top-level manifest merge and generate a list of proposed actions, which we consider "bids". We then make an "auction" among all the bids for each file and pick the most favourable. Some files might be trivial to merge with one ancestor, other files with another ancestor.
The most obvious advantage of considering multiple ancestors is the case where some of the bids for a file is a "real" (interactive) merge but where one or more bids just take on of the parent revisions. A bid for just taking an existing revision is very simple and low risk and is an obvious winner.
The auction algorithm for merging the bids is so far very simple:
- If there is consensus from all the ancestors, there is no doubt what to do. A clever result will be indistinguishable from just picking a random bid. The consensus case is thus not only trivial, it is also already handled perfectly.
- If "keep local" or "get from other" actions is an option (and there is only one such option), just do it.
- If the auction doesn't have a single clear winner, pick one of the bids "randomly" - just as it would have done if only one ancestor was considered.
This meta merge algorithm has room for future improvements, especially for doing better than picking a random bid.
Some observations
Experience with bid merge shows that many merges that actually have a very simple solution (because only one side changed) only can be solved efficiently when we start looking at file content in filemerge ... and it thus also requires all ancestors passed to filemerge. That is because Mercurial includes the history in filelog hashes. A file with changes that ends up not changing the content (could be change + backout or graft + merge or criss cross merges) still shows up as a changed file to manifestmerge. (The git data model has an advantage here when it uses hashes of content without history.) One way to handle that would be to refactor manifestmerge, mergestate/resolve and filemerge so they become more of the same thing.
There is also cases where different conflicting chunks could benefit from using multiple ancestors in filemerge - but that will require merge tools with fancy support for using multiple ancestors in 3+-way merge. That is left as an exercise for another day. That seems to be a case where "recursive merge" has an advantage.
The current manifest merge actions are very low level imperative and not symmetrical. They do not only describe how two manifests should be merged, they also describe a strategy for changing a context from a state where it is one of the parents to the state where it is the result of the merge with the other parent. I can imagine that manifestmerge could be simplified (and made more suitable for in memory merges) by separating the abstract merge actions from the actual file system operation actions. A more clever wcontext could perhaps also take care of some of the branchmerge special cases.
We assume that the definition of Mercurial manifest merge will make sure that exactly the same files will be produced, no matter which ancestor is used. That assumption might be wrong in very rare cases that really not is a problem.