Handling renames: svn vs. git vs. mercurial
How do each of these VCS handle renames?
I have found a lot of contradicting information stating that git tracks LOC (lines of code) instead of files, renames would therefore have no meaning for it.
6 Solutions collect form web for “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 --similarityfor 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 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.
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>“
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).
One more thing about git that hasn’t been mentioned yet, in addition to using heuristics to determine whether a rename has happened:
If a file or even an entire directory tree is renamed, copied, or moved, and nothing underneath is modified in any way, then the file or tree is actually stored as the same object inside the repository, and doesn’t take up any extra space.
If you modify it, then it’s stored as a new object, as usual.
I’m not sure about hg and svn, but I suspect their changelist-oriented architectures mean they behave differently in this scenario. It doesn’t really have any effect on usage, except it might give you reason to avoid moving or copying huge trees inside your repository.
git tracks contents, not files. i’m not sure about mercurial, but svn has to be explicitely told about renames