Git Beyond the D in DVCS

Jimmy’s post is a nice reminder of the advantages of a distributed version control system. But having ramped up on Git primarily through git-svn, I thought it would be worth enumerating some of the advantages I’ve found just in my local workflow:

1. Local Branching

More than anything, cheap local branches have changed how I work. I don’t know any developers that have the luxury of only working on one thing at a time. Not only do local branches provide the perfect mechanism to compartmentalize work on different features (or just ideas you’d like to try), but Git’s branching mechanism also works great for trying different approaches to the same problem. It’s trivial to reset a branch back a few commits if an idea doesn’t work out, or create a new branch from a few commits ago to see if there’s a better way.

The other advantage of local branches is that they’re not public unless you want them to be. Not only are centralized branches slower to work with and commit to, the ceremony required to set them (if you have permission at all) attaches a feeling of permanence—I’m less likely to commit something that’s not necessarily finished, but may still be useful. I don’t have these reservations with Git—my main repository at work has maybe 20 branches of refactorings, spikes and other works in progress that I’m free to revisit as time allows without feeling self-conscious about their current lack of polish.

2. Smarter Merging

Because of Git’s focus on file content rather than file location, it’s much better at resolving merge conflicts for you, especially across renames. That’s not to say you’ll never get conflicts, but they’re typically real conflicts—files deleted in one branch and modified in another, overlapping Visual Studio project file changes, etc. And for those you can hook up a 3-way merge tool like Beyond Compare or P4Merge to make quick work of them.

3. Pause and Resume

So your boss comes in and says something to the effect of “stop everything you’re doing and make this change for the CEO.” What do you do? In SVN you might create a patch file of your work in progress and revert. In TFS you might shelve your changes up to the server. In Git you have a two options that are built right in:

  1. git stash, which pushes the changes in your index and working directory onto a local stack of stashes from which you can pop at any time. Your typical workflow would look something like this:
    $ git stash save "I love interruptions!"
    $ git checkout master -b pti
    # ... save the day ...
    $ git checkout my-feature
    $ git stash pop

    The disadvantage of stashes is that they’re not explicitly tied to the branch where the work happened. The longer it takes to get back to what you were working on, the higher the chance you’ll forget you had some work stashed.

  2. Stage all your changes and make a temporary commit that you’ll later revert:
    $ git add -A && git commit -m "I love interruptions!"
    $ git checkout master -b pti
    # ... save the day ...
    $ git checkout my-feature
    $ git reset HEAD~

    The kicker is the final command, which moves the my-feature branch pointer back one (to HEAD‘s parent) but leaves the working directory unchanged—keeping your old work in progress. The advantage of this approach is that the temporary commit lives with the branch where the changes belong.

It doesn’t take long for both of these operations to become second-nature, greatly decreasing the impact of these interruptions.

4. Rewriting History

Pretending a temporary commit never happened is just one example of rewriting history, which becomes a fundamental operation for an intermediate/advanced Git user. But first, our cardinal rule:

Once a commit is pushed, it’s permanent.

History that has been shared with others is off-limits for rewriting unless you have explicitly communicated that history may be rewritten. For example, I routinely overwrite branches I push based on pull request feedback, but once something hits posh-git‘s master branch it’s there for good.

So with that out of the way, I present my corollary to the cardinal rule:

Until you push, pretend you’re perfect.

In the simplest case, this might simply mean using git commit --amend to fix a typo in the last commit’s message, or include changes to a file that you forgot to save. For more complex needs, like pretending you wrote your tests before the implementation, check out the amazing interactive rebase.

5. The Staging Area

Some people view Git’s staging area as an annoyance—an extra step between code and commit. I simply see it as your standard commit dialog with all the boxes unchecked by default. Checking all the boxes is as simple as git add -A, which I alias as git aa (git config --global alias.aa "add -A"). Or you can use git commit -a to stage all changes before commiting (though this won’t pick up additions or deletions).

The real power, though, comes from the ability to stage (git add --patch) and unstage (git reset --patch) parts of your changes before commiting. You can even use git checkout --patch to revert some changes in your working directory without affecting others, or even to pull in partial changes from another commit altogether.

I use this feature on an almost daily basis to separate cosmetic changes from refactoring from new feature work, without having to undo some changes so that I can commit the others. The result is more smaller, atomic commits that separate inert changes from those that actually change behavior.

Tip of the Iceberg

Ultimately these are just a few of the things that have convinced me Git is worth the learning curve, worth the cost to migrate, worth the cost to retrain your staff; and this doesn’t even touch on the advantages and flexibility of Git’s distributed nature. I just hope to pique your interest enough that you give it a try for yourself.

And if you’re already using Git, how does this compare with your list of must-have features?

Related Articles:

About Keith Dahlby

I'm a .NET developer, Git enthusiast and language geek from Cedar Rapids, IA. I work as a software guru at J&P Cycles and studied Human-Computer Interaction at Iowa State University.
This entry was posted in git. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #959

  • http://jason.lostechies.com Jason Meridth

    Excellent post.  There are a few tricks in there I have never used.  Thank you for sharing them Keith.

  • http://domenicdenicola.com/ Domenic Denicola

    > I use this feature on an almost daily basis to separate cosmetic changes
    from refactoring from new feature work, without having to undo some
    changes so that I can commit the others.

    Can you expand on this? I’ve often wanted some way to do this kind of thing, but cannot figure out from your brief description what the workflow would be.

    Thanks for the great post!

    • http://solutionizing.net/ Keith Dahlby

      There’s really not much of a workflow to it. When you get to a point where you find yourself with overlapping logical changes in a file you can use git add -p to stage and commit those changes in multiple parts.

      The easiest way to see this in action is to make several changes then do a git add -Ap to stage all files, piece by piece. For each hunk of changes you’ll be presented with several options: the most important are y/n (do or do not include the current hunk) and s (split this hunk into smaller hunks). Once you’ve staged something, try a git reset -p to go in the other direction.