Also available at

Also available at my website and on Twitter @toshafanasiev

Wednesday, 9 November 2011

Rolling back in Git

The organisation I work for uses TFS as its source control system but it's far from popular amongst the developers; I've noticed people going several weeks without committing changes to source control, using zip files for local change management and backup, and worse; going weeks at a time without even updating their source from the main repository.

The irony of this behaviour is that it is both motivated by and the root cause of horrific merge sessions tying up multiple developers for days at a time.

No doubt the problems we'd had to do with missing changes was down to something we were not doing right, but the fact remains that it was all too easy for us to make those mistakes.

The zip file approach doesn't appeal to me so I've recently been using Git as an intermediate source control system to give me lightweight branching and the ability to make very fine-grained commits without trampling all over my colleagues' work.

I'm really enjoying Git (though I do intend to try Mercurial for comparison, and because I like Python) and something I did today made me want to write about it.

I'd been making changes to a COM-heavy codebase to try to fix a bug that had been, well, bugging me for days and when I finally had the breakthrough it occurred to me that some of the things I had tried may not have contributed to the fix (I'm normally more scientific than this but that's COM for you - REGDB_E_CLASSNOTREG doesn't necessarily mean that the class is not registered).

Anyway, to cut what's becoming a long story short, I wanted to go back to the state of the code that "should have" worked, and selectively reapply the changes I'd made to ensure I was committing a minimal sufficient set back into TFS (to reduce merge headaches for my colleagues). What I was really impressed with was how easy and fast this sort of operation is with Git.

First, you rename the current branch (master, in my case) containing the whole set of changes, call it bug-fixed:

git branch -m bug-fixed

Next, you read the log to find the commit that corresponds to the point I started making the changes (this is where you're grateful you make regular, fine-grained commits):

git log --pretty=oneline -4

Note: the oneline option makes reading the commit signatures easier and -n specifies the last n commits - I knew it was only three or four commits ago.
Having found the commit you want, you check it out using just enough of its signature to disambiguate:

git checkout 2adff2

And finally you create a new master branch, using the current state as a starting point:

git checkout -b master

This very short (and quickly executed) sequence sets your master branch back in time to the appropriate commit, preserving the later changes in a named branch - genius, do that in TFS! (There are probably people who can but I'm not one of them.)

Furthermore, selectively applying the changes from the newly renamed bug-fixed branch couldn't be easier:

git checkout BRANCH [FILES]

pulls the versions of all the files specified by the space delimited list of files; (you can also use wildcards like *.h ) into your current branch. So to just bring over changes to some_class (declared in some_class.h, defined in some_class.cpp) from bug-fixed you'd do:

git checkout bug-fixed some_class.*

Just bear in mind that as well as bringing them over, it also adds them to the index so they won't show up in

git diff

you have to use

git diff --cached

I intend to give git-tfs a try at some point but I'd also like to investigate using hooks to manage interaction between a central Git repository and a TFS server.

No comments:

Post a Comment