12

From my understanding SVN is 'Easy to branch. Difficult to merge'. Why is that? Is there a difference how they merge?

0

2 Answers 2

23

Please see my Stack Overflow answer for a very concrete situation where Mercurial (and Git) merges without problems and where Subversion presents you with a bogus conflict. The situation is a simple refactoring done on a branch where you rename some files.

With regard to tdammers answer, then there is a number of misunderstandings there:

  • Subversion, Mercurial, and Git all track repository-wide snapshots of the project. Calling them versions, revisions, or changesets makes no difference. They are all logically atomic snapshots of a set of files.

  • The size of your commits makes no difference when it comes to merging. All three systems merge with the standard three-way merge algorithm and the inputs to that algorithm are

    • greatest common ancestor version
    • version on one branch
    • version on other branch

    It doesn't matter how the two branch versions were created. You can have used 1000 small commits since the ancestor version, or you can have used 1 commit. All that matters is the final version of the files. (Yes, this is surprising! Yes, lots of DVCS guides get this horribly wrong.)

He also raises some good points about the differences:

  • Subversion has some "voodoo" where you can merge from /trunk into, say, /branches/foo. Mercurial and Git does not use this model — branches are instead modeled directly in the history. The history therefore becomes a directed acyclic graph instead of being linear. This a much simpler model than the one used by Subversion and this cuts away a number of corner cases.

  • You can easily delay a merge or even let someone else handle it. If hg merge gives you a ton of conflicts, then you can ask your coworker to hg pull from you and then he has the exact same state. So he can hg merge and maybe he's better at resolving conflicts than you are.

    This is very difficult with Subversion where you're required to update before you can commit. You cannot just ignore the changes on the server and keep committing on your own anonymous branch. In general, Subversion forces you to play around with a dirty working copy when you svn update. This is kind of risky since you haven't stored your changes anywhere safe. Git and Mercurial lets you commit first, and then update and merge as necessary.

The real reason Git and Mercurial are better at merging than Subversion is a matter of implementation. There are rename conflicts that Subversion simply cannot handle even thought it's clear what the correct answer is. Mercurial and Git handles those easily. But there's no reason why Subversion couldn't handle those as well — being centralized is certainly not the reason.

4
  • 4
    great answer! I'd upvote twice if I could. :) I'd also add that SVN book you refer to in SO answer plainly admits that "Subversion's merge-tracking feature has an extremely complex internal implementation..." - this alone is a pretty good indication that feature is unreliable
    – gnat
    Jan 13, 2012 at 10:37
  • 2
    ... and even with that extremely complex implementation can't figure out the common ancestor correctly in any but simple cases.
    – Jan Hudec
    Jan 13, 2012 at 12:59
  • About delayed merges - don't get it - with SVN my coworker can update to/checkout trunk, and then merge from my branch into it.
    – Gill Bates
    Aug 11, 2013 at 11:57
  • @GillBates: I'm talking about a situation where you haven't started a branch for your work — when you're working on trunk in SVN. With a DVCS you can commit without sharing, but in SVN your svn commit will directly affect others who work on the same branch. Even if the two of us work on a branch in SVN, I cannot commit my work without having to immediately merge with your work. That makes commits somewhat scary — which is a scary property for a version control system! :-) Aug 13, 2013 at 17:54
6

The core problem lies in the way these systems represent a versioned directory structure.

Subversion's basic concept around which the whole system revolves is that of a version (or, in svn lingo, "revision"): a snapshot of a file at a certain point. As long as the history is perfectly linear, all is fine, but if you need to merge changes from two independent lines of development, svn has to compare the current versions of both, and then do a three-way comparison between the last shared version and the two head versions. Lines that appear changed in one of the heads, but not the other, can easily be resolved; lines that deviate exactly the same way in both heads are harder, but usually doable; lines that deviate in different ways are what makes svn say "I can't figure this out, human, please resolve this for me."

By contrast, git and mercurial track changesets rather than versions. The entire repository is a tree of changesets, each one depending on a parent, where a parent changeset can have any number of children, and the tree root represent an empty directory. In other words, git/hg says "first I had nothing, then this patch was applied, then that patch, etc.". When you need to merge two lines of development, git/hg not only knows what each head currently looks like, and what the last common version looked like, it also knows how the transition happened, allowing for much smarter merging.

Another thing that makes merging easier in a DVCS is an indirect consequence of separating the concepts of commit and push, and of allowing all sorts of cross-merges between any two clones of the same repository at any time. With svn, people tend to commit large changesets with often unrelated changes, because a commit is also an update on the central repository which affects all other team members; if you commit a broken version, everyone is going to be angry with you. Since most setups involve a networked svn server, committing also involves pumping data over the network, which means committing introduces a considerable delay to the workflow (especially when your working copy is outdated and you have to pull first). With git and mercurial, the commit happens locally, and because both are very efficient at handling local filesystems, it usually finishes instantly. As a result, people (once they get used to it) commit small incremental changes, and then when it works, push a dozen or so commits in one go. Then when merge time comes around, the SCM has much more detailed information to go by, and can do a better job resolving conflicts safely and automatically.

And then there's the nice details that make things even easier:

  • You can have multiple heads and still commit on either; unlike subversion, you don't need to combine pull, update and merge before committing again - the multiple heads just stay that way until you choose to merge
  • Directories are not treated specially; instead, the path is just considered one large filename, and all your directories have to be at the same revision at all times. This means you can't do the subversion voodoo where a project's subfolders are at different revisions, but it also means the working copy is less likely to become a huge unmanageable broken mess, and more interestingly, a move isn't represented as a delete-and-add (which would break entirely in svn if it weren't for the retrofit metadata), but simply as a rename; if you move a file, its entire history is preserved; merging can even apply changes to the moved file that were made to a non-moved version of the same file after the move, in another branch
  • Most of the time, you actually don't even need to branch: instead, you just clone the entire repository. Cloning is cheap, especially if it's done on the same filesystem, and if you decide you want to get rid of the clone, you just delete the directory it lives in and that's that. You don't even need to use hg or git for that.
  • There are few (if any) restrictions on what you can merge. You can have six clones of the same repository, and merge (or rather, push or pull; an explicit merge is often not required) from A to B, then C to B, then B to D, then C to D, B back to A, D to E, at any time and as often as you like.
  • You can test a merge by cloning one of the repositories you want to merge, and then pulling into that from the other. If it does what you want, you can push back to the real target, if it doesn't, you throw away the clone and start afresh.
6
  • 2
    I have to mention some corrections and adds to answer: 1. SVN revisions are global per-repository, revision represent all files in repo in some moment 2. Merge technology are basically common in SVN and DVCS - if file in merge files only changed the same way, merge will produce the same amount of conflicts for SVN and DVCS - all SCMs still operate on string-level, not a logical block 3. Large commits in SVN isn't result of architectural weakness, but because users often are lazy idiots - they ignore basic patterns. Branch|merge works in SVN, if /dev/brain and /dev/hands work Jan 13, 2012 at 8:15
  • 2
    Part 2: Smart merge in DVCS mostly happens because, contrary to Subversion, they track and handle moves|renames of files and SVN doesn't do it at all, thus - any operation, which process file, changed on one side and renamed on second, will fail. Branching with branches and with cloning are just different strategies of branching with the same rights to live, which to use "...depends from..." Jan 13, 2012 at 8:25
  • @LazyBadger: Au contraire. Subversion does track moves/renames, which causes spurious conflicts partly because it's handling of renames in merge is simply buggy and partly because there are corner cases which are difficult or impossible to handle correctly. The later is why git (and mercurial copied it) by design does not track renames and guesses them when merging. Which works fine if the contents is still similar enough to be merged (which is when you need it) and does not do silly things otherwise.
    – Jan Hudec
    Jan 13, 2012 at 9:47
  • @JanHudec - sorry, SVN handle not moves|renames at atomic one action (DVCS way - "rename"), but as "delete+...", thus - produce tree conflicts, where it doesn't happen in DVCS (true rename). Mercurial track renames explicitly (hg mv or hg addremove --similarity...), while Git uses heuristic, but both handle renames. I can get tree conflict even with 1 string difference in merged files! You have to re-learn some Subversion aspects, sorry. Jan 13, 2012 at 11:04
  • 5
    Now we're getting quite technical :-) Both Subversion and Mercurial track copies, not renames. Both systems track rename a b as copy a b; remove a and both do it in one atomic commit. The difference in merge behavior stems from different handling of corner cases and from Subversion allowing more merges than Mercurial and Git. Finally, Git detects renames at merge and log time — we're thinking of adding this in Mercurial too. Jan 13, 2012 at 11:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.