Note: this page is a little out of date, and describes work in progress, not yet visible on mainline. In the mean time, consider using the ZipperMerge pattern to help with difficult merges.


Notes on updating commands to work with multi-parent workspaces: MultiParentWorkspaceFallout


Everything below needs revision.

This should be reasonably simple to implement with the new roster merger.

Here's the idea -- we run roster_merge, and get back out a roster_merge_result. This is an insane roster, and a bunch of little notes on the conflicts that make it insane. (NB: "insane" is a technical term here; what it means is "if we called the check_sane function it would fail", or in other words, it is structurally invalid. The roster in a roster_merge_result has particular, well-specified invalid bits to it, each one corresponding to a particular conflict that needs resolution.)

Now, go through, and for each conflict fix up the roster in some special way. So, e.g., if there is a rename conflict, where two nodes want to be in the same place, they will both be detached in the roster. Re-attach them somewhere sensible under .mt-conflicts/. If there's a content conflict, attach new files with both versions plus the common ancestor somewhere under .mt-conflicts/, and also write out the file with conflict markers. And so on. In this way we end up with a sane roster, and a bunch of stuff under .mt-conflicts/. Also write out a textual description of the conflicts somewhere, for users to look at. Maybe make it basic_io, so tools can read it provide fancy UIs to resolving conflicts.

Write this roster out to the working copy. Make it possible to have multiple MT/work files (or make MT/work contain a revision instead of a cset, perhaps?), and have things like add/rename/drop/attr_set apply to all work csets, not just the single work cset.

So in this world, the way you resolve the above rename conflict is simple -- say you decide you want .mt-conflicts/foo/1 to get the name "foo", and to drop its competitor .mt-conflicts/foo/2. Just say monotone rename .mt-conflicts/foo/1 foo; monotone drop .mt-conflicts/foo/2. This magically gets resolved into the desired changesets.

Have some convenience commands, like monotone resolve *path/to/file/with/textual/conflict* should conveniently spawn their interactive merger, and if it works, mark that particular conflict as resolved. monotone resolve with no arguments cycles through all conflicts, as now. monotone resolved says "hey, I already resolved this problem, record that". (Maybe printing a warning if it sees conflict markers still in the file.) This sort of thing.

We need some checking to make sure that people don't check in versions that still have .mt-conflicts in them. Basically, you have to resolve conflicts before you commit. But we should prohibit the existence of .mt-conflicts in general, so as to ensure it's always available to use for writing out problems. Actually, this makes sense anyway -- the existence of conflicts is independent of whether we have multiple parents -- all of this applies to update, except it will only have a single parent.

Also, I don't think it makes sense to support restricted commits of merges?

(Later on, we might even want to allow committing with conflicts still present, so people can check in "merges in progress" and other people can help collaborate in resolving conflicts. But then we need a way to talk about conflicts involving files in .mt-conflicts.)

Todo: * work out details of how .mt-conflicts should look

A use case: http://article.gmane.org/gmane.comp.version-control.monotone.devel/5743 -- in particular, suggests may want to have some sort of merge preview.

Plan

A UI proposal for 'merge'

Synopsis: mtn merge -r OTHER [--to TARGET [--in DIRECTORY]]

If there are any local changes, and DIRECTORY is not given, then errors out with a helpful error message (suggesting revert, commit, or specifying a DIRECTORY). Then, if DIRECTORY is given, performs a checkout of TARGET to that directory and switches there; otherwise, if TARGET is given but DIRECTORY is not, then performs an update of the current workspace to TARGET. After all this, merge OTHER into the current workspace.

If OTHER is a descendent of the current workspace, then simply update to it, but keep the current branch.

Branch handling for TARGET/DIRECTORY is done exactly as it is done for checkout and update. At the end of the merge, we should probably print out the current branch, as well.

We should make sure that 'commit' is happy to commit a revision that already exists, with a new branch/changelog.

propagate FROM TO becomes merge -r h:FROM --to h:TO -- unless you're already in the branch workspace you want to propagate into, in which case it's just merge -r h:FROM.

Another switch: --preview, which simply describes what conflicts would occur should you actually perform the given merge.

Another switch, demanded by graydon and friends :-): --direct-to-db (FIXME: need a good name for this), which is incompatible with '--in', and commits directly (maybe with popping up textual conflict resolvers as we do now).