The internal data models are fundamentally different.
Fundamentally, in SVN, when you look at the history of a branch, you only see what has happened in that branch. So when you merge from branch B
to branch A
, the history of branch A
will contain one large commit containing all the changes made explicitly to B
since it was branched.
In the first versions of SVN, if you had to merge branch B
into branch A
once more, you had to manually specify which revision range of branch B
you wanted to merge in order to avoid merging the same revisions twice. The clever developer would of course use a commit message like 'Merged in B:1234'.
SVN 1.5 "fixed" this. But it did not change how merges are applied fundamentally. It merely added some extra metadata to branch A
, letting SVN know that revision 1234 had been merged in, allowing SVN to automatically choose the correct revision range.
But this solution is basically a workaround for a data model that fundamentally doesn't support track what has been merged.
Merging two branches is a relatively simple example. But imaging this more complex scenario
- Create branch
A
from trunk
, and make a few commits here
- Create branch
B
from A
and make a few commits here
- Make a few commits in
trunk
and A
- Merge
B
into trunk
- Merge
A
into B
- Merge
A
into trunk
- Merge
B
into trunk
(this shouldn't actually do anything)
Handling this correctly using the metadata model becomes extremely complex (I don't know if SVN does indeed handle this scenario correctly, and I don't feel inclined to test it out).
Handling this scenario in git is extremely simple.
In git, every time you commit, the internal object representing that commit contains a reference to the previous head. When you merge in a branch, the commit contains references to the previous head of all the branches being merged (you can merge more than one branch at a time in git)
Therefore, when you examine the history of a single commit in git, you can see all history, you can see when it was branched, when it was merged, and you can see the history of both branches between the branching and the merging.
Thus when merging in a branch that has been partly merged, it is extremely simple to determine what has been merged already, and what has not.
I have no experience with Mercurial, but I suspect that its internal workings are similar to git.
So fundamentally, for SVN, it was a design goal to make branching cheap. But in git, it was a design goal to make merging cheap.
Lastly, last time I used SVN, it was not able to handle merges, where a file was renamed in one branch, and modified in another.