How does the 3 way merge in Mercurial/Meld work? - mercurial

I'm working on a project where I have a commit that introduced a feature with major problems that weren't discovered immediately. Now I want to completely remove that revision while keeping the work following it but i'm having a hard time wrapping my head around this 3 way merge. Here is a simplified graph of my project.
o changeset: 134:7f81764aa03a
| tag: tip
| parent: 128:451d8a19edea
| summary: Backed out changeset 451d8a19edea
|
| # changeset: 133:5eefa40e2a29
| | summary: (Change I need to keep keep)
| |
*snip 3 commits*
| o changeset: 129:5f6182a97d40
|/ summary: (Change I need to keep keep)
|
o changeset: 128:451d8a19edea
| summary: (Change that introduced a major problem)
|
o changeset: 127:4f26dc55455d
| summary: (summary doesn't matter for this question)
If I understand this correctly, r127 and r134 are exactly the same. When I hg up -C -r 133 and then run hg merge, Meld pops up with three forms of one of my files: local, base, and other. local seems to be r133 but i'm having a hard time wrapping my head around what "base" and "other" mean.

Local is r133
Other is r134
Base is r128 (the common ancestor to both r133 and r 134)
When you perform a 3 way merge it compares all three of those together to help you decide what to take and from where. By seeing what change is in the other revision and what the common ancestor looked like you are able to make a much more informed decision about what to keep and what to change.

Your question is really confusing, but here are some information that may help you.
What is base?
Base is the unmodified version of the revision that you have currently checked out and worked on. where possibly other changes have forked off (you can have revisions in between your current local and base!). its just where the nearest revision where no other fork has diverted from afterwards (same parent) (in your case r128)
What is head?
Head is the latest revision in version control. if you work alone on only one copy it will probably be base. but a co worker might have modified the same file and checked it into version control, then head is later than your base.
What is local?
Local is your modified version (in your case r133)
What is other?
Other is the some fork/branch that has also your Base as parent (in your case r134)
How does 3-way merge work?
3 way merge works (at least in meld) hierarchically. usually from left to right like this:
local > base > other / head
local/base is mostly trivial because its just what you modified
then you can merge your changes into the head revision or the one of your co worker or whatever.
There can be multiple other/head revisions, but then it's not your job to merge in and therefore more than 3 way compare doesn't make sense.

Related

Condensing a mercurial repository - recommanded way?

Let's say I have a repository 'Main', and Max and co work on a clone each. Max has some local commits ('f'&'g') that are not yet pushed to 'Main'. This is how it looks now (pipes being pushs/pulls):
A--B1--B2--C--D1--D2--D3--E (Main)
| | | |
A--B1--B2--C--D1--D2--D3--E--f--g (Max)
'B1' and 'B2' as well as 'D1', 'D2' and 'D3' are changes that only make sense together. We would like to combine 'B1' and 'B2' to a single changeset 'B' and combine 'D1', 'D2' and 'D3' to a single changeset 'D'. The new structure should look like this:
A--B--C--D--E (Main)
| | |
A--B--C--D--E--f--g (Max)
My (main) question is: What is the reccommended way of doing this?
Now let's make things worse:
We have a branch that was merged within the change-sets that we want to collapse. It would look like this:
A--B1--B2--C--D1--D2------D4--E (Main)
| | \-------D3-/ |
| | |
A--B1--B2--C--D1--D2------D4--E--f--g (Max)
\-------D3-/
The new history should look like this:
A--B--C--D--E (Main)
| | |
A--B--C--D--E--f--g (Max)
How would you do that?
Thanks in advance.
It depends on how much effort you want to put into this. While I don't know a solution within Mercurial itself (I only know history editing functions which can't cope with merges), Git does have the functionality you need:
If I would really have to do such an operation, I would
Try to convince the management that this is not worth it
Try harder to convince the management that this is not worth it
Make a backup! The following steps involve destructive operations, so consider this as not optional. You have been warned.
exort the repo with hg-git into a git repository
export the complete (git) history into a fast-import-stream with git fastexport --no-data --all > history.fi
Create a Pseudohistory by editing history.fi, dropping your unwanted revisions
import the adjusted history into the git repo with ``git fast-import -f < history.fi`
check extensively if the newly created history is in fact the way you want it to have
clone Max into a local work repository
Remove successors of commmit A in the local work repository
pull your updated history back from git (again with hg-git) into the local work repository
check, if the Mercurial history matches your expectation (diffs of commits between the new and old repos, metadata (time stamps, committer names, ...)
Remove successors of commmit A in every repo (Main, Max and every developer clone)
hg push -r E Main the partial history back to Main out of the work repository
hg push -r g Max the complete history back to Max out of the work repository

Can I retrospectively move Mercurial revisions to a branch?

I have a project with a natural long-term branch cum fork; I won't want to re-merge this with the default branch. I initially managed this fork with a bookmark, but on reflection it seemed to make more sense to have this as a named branch, so (without thinking about it too much) I created a branch at the bookmarked head (4).
o 7:default
|
| o 6:fork
| |
| o 5:fork
| |
| o 4:default
| |
o | 3:default
| |
| o 2:default
|/
o 1:default
|
o 0:default
But this means that I now have a branch plus a lonely head (4) on the default branch which isn't going to go anywhere (because it's really part of the branch/fork, and I don't plan to commit to (4) on the default branch). But this means that the repository is now somewhat untidy: I have a fork which exists partly on the default branch and partly, later, on the named branch and which, because it's on default looks as if it should be merged later. And oooh, that untidiness is a terrible thing (perhaps I should get out more).
What I'd like is either (a) to magically put selected revisions (ie, the ones leading to the now lonely head) onto the named branch, or (b) go back to plan A (bookmarks) and retrospectively put revisions on the named branch (5 & 6) back on the default branch and add a bookmark.
Possibilities:
There is a question Move branch start in Mercurial which overlaps with this, but is framed in terms of an expectation that the branch will be re-merged in future. Also, I'd quite like to retain the intermediate (post-fork pre-branch) revisions as being on the branch.
Use hg convert to create a copy of the repository minus the fork, create the branch, and start hand-applying changesets. Messy and rather error-prone, but doable (there aren't too many revisions in the branch).
Acknowledge that life is messy and full of disappointments, that history is history, and that I should stop worrying about inessentials.
or...
I'm holding out for magic.
Ghost merge. Merge revision 4 into revision 7. Discard all
changes of rev. 4 when doing the merge.
Close the head. Update to rev. 4, close the branch there.
Rebase. If revisions 2, 4, 5, 6 are in the draft phase. You can rebase starting from rev. 2, moving all descendants to another branch.
Options 1 and 2 will only hide the unwanted head (history unchanged). To rewrite history, go with option 3.
Rebase to default. You can also rebase from revision 5, moving that branch back to default. Using bookmarks again, as the branch never existed.

How do I merge two heads on Mercurial?

Please, help me! I'm not familiar with Mercurial.
I have a message in my bitbucket telling me that my "default branch has multiple [two] heads", which is not what I want. The branches are both labeled "Default." I've tried hg merge and I get "abort: nothing to merge."
When I do hg heads, only one gets listed.
changeset: 4:fb6f0d961015
tag: tip
user: Name <my_email>
date: Fri Feb 07 03:39:23 2014 -0800
summary: Folder
Have you pulled changes from the server? It sounds like you have one head locally, but when your changes are combined with what is on the server it produces two heads...
If your changes 'C' are based on change 'B', but someone else has also made change 'D' based on 'B' ...
C D
| |
B /
|
A
You see A-B-C on your machine (only one head), but when it is pushed to the server it would create two.
If that is the case, you need to pull the latest changes from the server and then do the merge.
If you just performed a hg pull you probably just need to do
hg update -r <revision>
where is the one that resulted from pulling. In general you may find it convinient to combine the two operation with
hg pull -u <repository>

Move branch start in Mercurial

My problem is similar to Mercurial move changes to a new branch, but is not exactly the same.
A colleague of mine started working on a new feature and issued a few local commits. Then he noticed "Oh well, that should go into a named branch" and thus created one. The problem is, the commits before should have been in that branch already. So the simplified history now looks a little like this:
O 7: default
|
O 6: default
|
| O 5: newbranch
| |
| O 4: default
| |
| O 3: default
| /
O 2: default
|
O 1: default
So default now has two open heads: 7 and 4. I'd like to get rid of the open head 4 but of course still have 3 and 4 merged back into the "main" default later on when we merge newbranch back into main (merging them now is undesired as it would bring changes into default that we don't want there yet).
The question is: what's the best way to handle this ?
Do a --close-branch on 5, create a new branch off 2 and re-apply revisions 3-5 ?
As far as I've understood there's no good way to simply say "newbranch started at 3 instead of 5".
Update: Since this is has already been pulled by others I don't want to rebase the revisions in question. Forcing the colleague to recreate/switch branches would be OK since that would affect only one person instead of 5.
To get rid of the extra default head, you could:
hg update -r 4
hg commit --close-branch -m "Closed extra default head. The changes from this branch will be merged along with 'newbranch', once it is complete."
When you eventually merge newbranch, it will include revisions 3-5 plus any others that you commit on top of 5.
The only potential problem with this is that revisions 3-4 will not be labeled as part of the named branch. If this is really important for you, then you should close both the extra default head and the newbranch head and start over with a new branch rooted at revision 2.

Mercurial: how can I see only the changes introduced by a merge?

I'm trying to get in the habit of doing code reviews, but merges have been making the process difficult because I don't know how to ask Mercurial to "show only changes introduced by the merge which were not present in either of its parents."
Or, slightly more formally (thanks to Steve Losh):
Show me every hunk in the merge that wasn't present in either of its parents, and show me every hunk present in either of its parents that isn't also present in 3.
For example, assume I have a repository with two files, a and b. If "a" is changed in revision 1, "b" is changed in revision 2 (which is on a separate branch) and these two changes are merged in revision 3, I'll get a history which looks like this:
# changeset: 3
|\ summary: Merged.
| |
| o changeset: 2
| | summary: Changing b
| |
o | changeset: 1
|/ summary: Changing a
|
o changeset: 0
summary: Adding a and b
But if I ask to see the changes introduced by revision 3, hg di -c 3, Mercurial will show me the same thing as if I asked to see the changes introduced in revision 1, hg di -c 1:
$ hg di -c 3
--- a/a
+++ b/a
## -1,1 +1,1 ##
-a
+Change to a
$ hg di -c 1
--- a/a
+++ b/a
## -1,1 +1,1 ##
-a
+Change to a
But, obviously, this isn't very helpful - instead, I would like to be told that no new changes were introduced by revision 3 (or, if there was a conflict during the merge, I would like to see only the resolution to that conflict). Something like:
$ hg di -c 3
$
So, how can I do this?
ps: I know that I can reduce the number of merges in my repository using rebaseā€¦ But that's not my problem - my problem is figuring out what was changed with a merge.
The short answer: you can't do this with any stock Mercurial command.
Running hg diff -c 3 will show you the changes between 3 and its first parent -- i.e. the changeset you were at when you ran hg merge.
This makes sense when you think of branches as more than just simple changesets. When you run hg up 1 && hg merge 2 you're telling Mercurial: "Merge changeset 2 into changeset 1".
It's more obvious if you're using named branches. Say changeset 2 in your example was on a named branch called rewrite-ui. When you run hg update 1 && hg merge rewrite-ui you're effectively saying: "Merge all the changes in the rewrite-ui branch into the current branch." When you later run hg diff -c on this changeset it's showing you everything that was introduced to the default branch (or whatever branch 1 happens to be on) by the merge, which makes sense.
From your question, though, it looks like you're looking for a way to say:
Show me every hunk in this changeset that wasn't present in either of its parents, and show me every hunk present in either of its parents that isn't also present in 3.
This isn't a simple thing to calculate (I'm not even sure I got the description right just now). I can definitely see how it would be useful, though, so if you can define it unambiguously you might convince one of us Mercurial contributors that read SO to implement it.
In order to do code reviews you really want to see just the changes in the project that you are reviewing. To that end we use a new branch for each story and use pull requests to spotlight the changes, having merged all changes into the story branch before creating the pull request. We host our code on bitbucket and the review / pull request tools are really very good, offering a side by side diff.
Pull requests with side-by-side diffs