A year ago, I was happily using Subversion for my version control, and reading about Git. It sounded intriguing, and there were lots of articles about the life-changing enlightenment of switching to Git. Frictionless branching and merging? A whole new elegant model of file management? Plus I get to think about directed acyclic graphs? Cool.
For the last few months, I’ve been using Git pretty much every day. But sadly, I’m still waiting for the epiphany. Here’s how I reckon up the pros and cons. Maybe it will help someone else with this transition. (Please note that I don’t have the pleasure of knowing other nifty new distributed version control systems like Bazaar and Mercurial.)
Let’s start with the good stuff.
- Git really is faster. I’m still startled sometimes to realize that my command already finished running. In particular, something like switching between branches is very smooth. So far, so groovy.
- Oh, and Git manages its internal files better. It puts the whole version-control business in a single .git directory, rather than spreading it out in .svn directories all over the place. I hate those .svn directories: they give me false positives when I’m searching my files, and it’s pretty easy to mix two incompatible working copies accidentally, throwing Subversion into a subtle confusion that requires checking out the whole project again. Well done Git!
Here’s what bugs me, though: Git complicates my life without giving me much in return. Here are the bad points.
- Git adds extra steps to the flow of content between your local files and the remote repository. This is Exhibit A for me, as it annoys me every day. In Subversion, you have the local working copy, and the remote repository. That’s two states of the code, with clear and distinct purposes. In Git, you have three more steps in between those two: the staging area, the local repository, and the remote branch. That’s five states of the code, all of which are different, and all of which you need to understand.
Let’s see how this plays out in the daily workflow. In Subversion, you use “svn update” to move the code downstream, and “svn commit” to move it upstream. In Git, the downstream commands are “git fetch” (to move code to the remote branch) and “git merge” (to the local repository, I think). The upstream commands are “git add” (to the staging area), “git commit” (to the local repository), and “git push” (to the remote repository).
That’s five commands instead of two. You can use “pull” to mean “fetch and merge”, but some authorities will advise you not to. You can also use “commit -a” to avoid the “add”, but you still have to be aware of it.
The staging area is also known as the “index” and the “cached” files. Yes, you will find all three of these terms used in documentation and tutorials, and they all mean the same thing. The staging area allows you to commit only some of your work, rather than all of it. This is something I am occasionally tempted to do, but not something I want to worry about all the time.
The remote branch (for example, “origin/mybranch”) is a strange beast. It’s a local pointer to the remote repository, and it may or may not be up to date. It’s involved in the downstream but not the upstream flow, for some reason.
If you want to clean up an unused branch, that’s fine, but remember that you have to do it in both the local and remote repositories, with “git branch -d mybranch” and the amazing “git push origin :mybranch”, respectively. (To be fair, that second command seems to have embarrassed even the Git developers, so they provided the somewhat clearer “git push origin –delete mybranch”.)
In my current project, we maintain extra repositories on a staging server for deployment, which compounds the extra-steps problem. I find myself the most Git-savvy person on the team, so it’s my job to explain the Git process to my colleagues, awkwardly: “Yes, I know it’s a pain, but that’s how it works.” We keep things under control by maintaining detailed sample scripts for everyone to refer to. We would do the same if we were in Subversion; it’s just that the scripts would be shorter.
- Git commit names are annoyingly opaque. This is the kind of thing you don’t miss till it’s gone, but consider the Subversion revision numbers 546, 547, and 987, and how brief and informative they are. In Git, these would probably be the commits 7b558b6, e329317, and 0cccacb. (Or maybe you’re unlucky and that last one is not unique, so you have to say 0cccacb1 instead. Yes, it’s unlikely, but there are no guarantees.)
- Git forces me to learn obscure things that don’t seem to relate to my work. For example, some repositories are “bare repositories”; I don’t remember what that means, but I needed to know it briefly once. Sometimes the repository is in a state called “detached HEAD”, which means (roughly speaking) that it’s not at the current state of one of your branches, but at an older state instead. There’s an advanced feature called “reflogs” which I might need someday to retrieve data in my repository that I lost. (Isn’t that something that should be standard, not obscure, in a version control system?)
To be fair, Subversion made me learn what “peg revisions” are, and what “svn:mergeinfo” properties are. I guess those just make more sense to me than the Git examples above.
Finally, there are a couple of points that are neither good nor bad, but neutral.
- Git allows the topic-branch development style, where you maintain one short-lived branch for every task you work on. Like many people, I learned about this style from distributed version control. I love it. It’s an excellent way to switch between tasks, and maps well to the way I actually think about the state of the project.
But you don’t need Git to do this! Subversion has “svn switch”. It does have an annoying problem that if you specify the wrong URL to switch to, it will trash your local copy rather badly. However, Subversion seems to have fixed that problem in version 1.7. (It also fixed the multiple .svn directories problem I complained about above.)
- Git allows cleaning up your history by rearranging the commits in your local repository before they go out to the remote repository. See, for example, the “rebase” command. This is kind of neat, but I’ve never found a compelling reason to do it. I’m happy to have the remote repository record what actually happened.
I’m still using Git. I want to give it a fair trial. (After all, I remember the transition from CVS to Subversion. Do I want to go back to CVS? No, I do not.) And here’s what I think: Git’s an interesting experiment. Perhaps my eureka moment is still to come. Till then, if I have a choice, I’ll stick with Subversion.