Handling renames: svn vs. git vs. mercurial

  • Git doesn't track renames at all, but uses heuristic to re-discover them during merge etc.
  • Mercurial tracks renames (the origin version and origin file is recorded) and uses that information during merges. So you have to explicitly tell hg about renames with hg mv, or use hg addremove --similarity for auto-discovery. There has been some talk about adding heuristics during merge too.
  • Svn tracks renames, but I don't know how good it deals with them during merges (never actually tested that).

Git

Git is different in that it doesn't do rename tracking, which means that it doesn't need to be told about renames by using SCM commands to do rename (or run autodetection script to mark renames before commit), and doesn't save such information in the repository, but it does rename detection. This means that it finds renames using filename and file contents similarity based heuristic algorithm, both during merge, and for diff when requested via -M option (or configured using diff.renames config option).

The advantages of this method are the following:

  • renames doesn't need to be marked (or detected) explicitely: the renames can come from a patch, or can be done vie filemanager or graphical interface
  • the similarity detection algorithm can be improved, and is not frozen at the time of commit as in the case of detecting renames to mark them before commit, and saving this information in repository; it is also easier to deal with rename detection mistakes if they are not frozen in history
  • it follows Git ophilosophy that it is contents that matters; see how git blame (and graphical frontends to it like "git gui blame") can follow movement of blocks of code across file boundaries, something that is more generic than wholesame renames of files.
  • the same mechanism is responsible for handling renames during merges; using rename detection means that it can be done for 3 commits that merge is ultimately based on, and there is no need to carefully follow history, noting each rename - or a non-distributed concept of canonical name of a file

Note that pathspec filtering do not work well with rename detection; if you want to follow history of a file across renames use "git log --follow <filename>"


In practice:

Git detects renames automatically. (Incidentally, I've heard claims that git can detect when you move a function from one file to another. My initial tests seem to indicate that this is not the case, however.)

With Mercurial, you have to explicitly tell it about renames, either by hg mv, or the --similarity option of hg addremove, or TortoiseHg's "guess renames" option, or certain tools e.g. VisualHg will flag the renames for you. If you want to use the Git approach with Mercurial, I've written an extension to detect renames at commit time, but it's at a very experimental stage at the moment.

Subversion doesn't handle renames at all. It records a rename as one file being removed and another file being added. This means, for instance, that if Alice changes a file and Bob renames it, you get a tree conflict. This happens whether you are doing full-blown branching and merging or simply svn update. Rename tracking is planned for Subversion 1.8, due out next year sometime.


You have heard right, sort of.

Git operates on the contents of the files, not the files themselves, so renames are technically meaningless to it. To git, a rename looks like file A disappeared and file B appeared with the same content as A. But git is actually pretty good at figuring out when a file has actually been renamed.

Try it: rename a file, then run 'git rm oldname' and 'git add newname' to tell git to stage the changes, then run 'git status' to see what git thinks it's doing -- you'll see that it tells you the file has been renamed. I'm not sure that means anything later, though. Look at a commit with 'git show ' and you won't see any mention of a rename, just a bunch of lines removed from one path and added to another.

Alternatively, you can also use the 'git mv' command to rename a file. It doesn't change how git sees the operation, it just effectively does 'mv oldname newname', 'git rm oldname' and 'git add newname' in one step.

For an overview, of Mercurial, see tonfa's answer.

SVN, on the other hand, cannot detect renames but must be told about them with the 'svn mv' command. When told, however, it tracks renames as "first class" changes, so when viewing the changelog later you'll see that the change was a rename.

I wouldn't suggest choosing a SVN over git or mercurial based on this feature, though. There are much larger and more important differences between the tools. I'd first decide whether you want a distributed version control system (git or mercurial) or a centralized version control system (svn).