It’s been about a year since I created the Mercurial nextMerge revsets alias. It has worked well, but recently the complex Enterprise Web Library repository exposed some flaws, especially the script’s obliviousness to the need for recursion when delving down the graph to determine the safe merges that must be done in preparation for a “no-op” merge. I’ve just published a new version that fixes all known flaws. It deals with the recursion issue not by actually doing recursion (which isn’t possible with revsets), but by returning multiple changesets to let the user know that nextMerge must be rerun.

Here’s the updated code, which you can drop into your Mercurial configuration file:

# The first parameter is the merge destination. The second is the set of changesets that you'd ultimately like to merge.
# If multiple changesets are returned, recursion is required. Please rerun with the head changeset as the second argument.
# Implementing this within Mercurial or as an extension would be better because we'd be able to automatically perform the recursion.
nextMerge($1,$2) = nextMergeForSingleHead( $1, oldest( heads( $2 – ancestors($1) ) ) )
# In the following aliases, each parameter should be a single changeset.
# only one term at a time should return results
nextMergeForSingleHead($1,$2) = headMerge($1,$2) | safeMergeCandidateAndAncestors($1,$2) | noOpMerge($1,$2)
# the unmerged head, if only a single GCA exists
headMerge($1,$2) = $2 & firstGcaChangesets($1,$2)
safeMergeCandidateAndAncestors($1,$2) = ancestors( safeMergeCandidate($1,$2) ) & unmergedAncestors($1,$2)
# the oldest unmerged parent of the oldest multi-GCA changeset
safeMergeCandidate($1,$2) = oldest( unmergedAncestors($1,$2) & parents( oldestMultiGcaChangeset($1,$2) ) ) & noOpMergeMask($1,$2)
# The oldest unmerged changeset that descends from the first GCA and one or more other GCAs. Will always be a merge changeset.
oldestMultiGcaChangeset($1,$2) = oldest( ( unmergedAncestors($1,$2) & descendants( firstGca($1,$2) ) ) – firstGcaChangesets($1,$2) )
# the changesets that descend from the first GCA and not from any others
firstGcaChangesets($1,$2) = descendants( firstGca($1,$2) ) – descendants( greatestCommonAncestors($1,$2) – firstGca($1,$2) )
noOpMergeMask($1,$2) = not descendants( ancestors( noOpMergeChangesets($1,$2) ) )
# the oldest head of the no-op merge changesets
noOpMerge($1,$2) = oldest( heads( noOpMergeChangesets($1,$2) ) )
# the GCA-child merge changesets that, when merged into the destination, will eliminate GCAs without changing anything
noOpMergeChangesets($1,$2) = gcaChildMergeChangesets($1,$2) – descendants( unmergedAncestors($1,$2) – merge() )
# the unmerged merge changesets that are children of a GCA
gcaChildMergeChangesets($1,$2) = unmergedAncestors($1,$2) & merge() & children( greatestCommonAncestors($1,$2) )
# The first GCA of the oldest unmerged child of a GCA. If the oldest unmerged child of a GCA is a merge with two GCA parents, one is chosen arbitrarily.
firstGca($1,$2) = first( parents( oldest( children( greatestCommonAncestors($1,$2) ) & unmergedAncestors($1,$2) ) ) & greatestCommonAncestors($1,$2) )
unmergedAncestors($1,$2) = ancestors($2) – ancestors($1)
oldest($1) = first( sort($1,rev) )
# Similar to the *ancestor* predicate, but not limited to one result. Each parameter should be a single changeset.
greatestCommonAncestors($1,$2) = heads( ancestors($1) & ancestors($2) )
# Rationale:
# Merging algorithms depend on finding the Greatest Common Ancestor (GCA) of the two changesets being merged, so they'll work most predictably when this is not
# ambiguous. This means that, in general, we should avoid these "criss-cross" merges. When we cannot avoid them, we should first perform merges into the
# destination that get us in a position to perform a criss-cross merge that consists solely of merge changesets (i.e. a "no-op" merge). We can repeat this
# process until only a single GCA remains, at which point we can safely complete the original merge.
# References:

view raw


hosted with ❤ by GitHub