Search code examples
gitsvnmercurialmergedvcs

Merging: Hg/Git vs. SVN


I often read that Hg (and Git and...) are better at merging than SVN but I have never seen practical examples of where Hg/Git can merge something where SVN fails (or where SVN needs manual intervention). Could you post a few step-by-step lists of branch/modify/commit/...-operations that show where SVN would fail while Hg/Git happily moves on? Practical, not highly exceptional cases please...

Some background: we have a few dozen developers working on projects using SVN, with each project (or group of similar projects) in its own repository. We know how to apply release- and feature-branches so we don't run into problems very often (i.e., we've been there, but we've learned to overcome Joel's problems of "one programmer causing trauma to the whole team" or "needing six developers for two weeks to reintegrate a branch"). We have release-branches that are very stable and only used to apply bugfixes. We have trunks that should be stable enough to be able to create a release within one week. And we have feature-branches that single developers or groups of developers can work on. Yes, they are deleted after reintegration so they don't clutter up the repository. ;)

So I'm still trying to find the advantages of Hg/Git over SVN. I'd love to get some hands-on experience, but there aren't any bigger projects we could move to Hg/Git yet, so I'm stuck with playing with small artificial projects that only contain a few made up files. And I'm looking for a few cases where you can feel the impressive power of Hg/Git, since so far I have often read about them but failed to find them myself.


Solution

  • I do not use Subversion myself, but from the release notes for Subversion 1.5: Merge tracking (foundational) it looks like there are the following differences from how merge tracking work in full-DAG version control systems like Git or Mercurial.

    • Merging trunk to branch is different from merging branch to trunk: for some reason merging trunk to branch requires --reintegrate option to svn merge.

      In distributed version control systems like Git or Mercurial there is no technical difference between trunk and branch: all branches are created equal (there might be social difference, though). Merging in either direction is done the same way.

    • You need to provide new -g (--use-merge-history) option to svn log and svn blame to take merge tracking into account.

      In Git and Mercurial merge tracking is automatically taken into account when displaying history (log) and blame. In Git you can request to follow first parent only with --first-parent (I guess similar option exists also for Mercurial) to "discard" merge tracking info in git log.

    • From what I understand svn:mergeinfo property stores per-path information about conflicts (Subversion is changeset-based), while in Git and Mercurial it is simply commit objects that can have more than one parent.

    • "Known Issues" subsection for merge tracking in Subversion suggests that repeated / cyclic / reflective merge might not work properly. It means that with the following histories second merge might not do the right thing ('A' can be trunk or branch, and 'B' can be branch or trunk, respectively):

      *---*---x---*---y---*---*---*---M2        <-- A
               \       \             /
                --*----M1---*---*---/           <-- B
      

      In the case the above ASCII-art gets broken: Branch 'B' is created (forked) from branch 'A' at revision 'x', then later branch 'A' is merged at revision 'y' into branch 'B' as merge 'M1', and finally branch 'B' is merged into branch 'A' as merge 'M2'.

      *---*---x---*-----M1--*---*---M2          <-- A
               \       /           / 
                \-*---y---*---*---/             <-- B
      

      In the case the above ASCII-art gets broken: Branch 'B' is created (forked) from branch 'A' at revision 'x', it is merged into branch 'A' at 'y' as 'M1', and later merged again into branch 'A' as 'M2'.

    • Subversion might not support advanced case of criss-cross merge.

      *---b-----B1--M1--*---M3
           \     \ /        /
            \     X        /
             \   / \      /
              \--B2--M2--*
      

      Git handles this situation just fine in practice using "recursive" merge strategy. I am not sure about Mercurial.

    • In "Known Issues" there is warning that merge tracking migh not work with file renames, e.g. when one side renames file (and perhaps modifies it), and second side modifies file without renaming (under old name).

      Both Git and Mercurial handle such case just fine in practice: Git using rename detection, Mercurial using rename tracking.

    HTH