Wednesday, January 19, 2011

How to rewrite history with GIT - Part - 1

One of the truly awesome feature of GIT is rewriting history.  This is one of the best and most useful feature that GIT has to offer.  Other distributed version control systems (read Mercurial(HG)) do not offer this feature out of the box (additional extensions are needed to achieve this in Mercurial).  Over and above that, with Mercurial the process of rewriting history is pretty painful.  But when it comes to GIT the support to rewrite history is build right in the core.  The elegance with which GIT performs this task is amazing!

What does rewriting history mean?

Rewriting history could mean many things.

  • Changing the message on one of the previous commits
  • Removing the commit completely
  • Merging two or more commits in one commit.
  • Re-ordering the commits
  • Editing a previous commit to include/remove files

All of these are extremely useful features.  When used correctly could save you from a lot of trouble!

Why should I use it?

For people who are used to central version control systems (like SVN, CVS etc), rewriting history feature is unheard of.  Initially it might feel that you do not need this feature.  What is the user of it?  Take my word on this, its really really helpful feature.  It gives you the power to correct your mistakes even after you have committed!  Isn't that awesome!

Like it already?  

Show me how to do it.  How do they do it?

In this post we will look at how to do the first three points.  In the next post I will cover remaining two points.

Lets start, the current situation of my GIT repository is that, I have synced up with my remote or the public repository and there are no local commits.  

git log shows that I have pushed all my commits to SVN. Although in my example, I am using GIT over SVN. But the rewriting history feature can be used with pure GIT setup as well.

OK, lets start by making some local commits.

I hate to remember those ugly admin passwords.  Lets add a password.properties file that holds the administrator password.

Lets commit this file

Never do this! Never every commit administrator password to your repository. This is a mistake (Yes, I realize that!), we will correct this later.

Let go on for now, lets make more local commits.  Lets say there is a long pending bug we wanted to fix.  Lets fix it!

Lets commit our bug fix.

Again note that, commit messages like "Fixing bug" are hardly helpful. Please please please, always put informative commit message. This not only helps others but it will help you as well.  Providing informative commits message helps get an idea of why the commit was made and what to expect in it. Its just common sense!

We have made another mistake (by not providing informative commit message), but will fix it later.

Moving on, making more changes.

Committing the refactored file

Writing a test case to prove that the performance of the app has actually improved.

Committing

Lets stop at this point and have a look at what we have done so far.

Basically we have made 4 local commits which have not been pushed to the remote repository. We suddenly realize that we have done a few blunders!

  • Committing the administrator password to the repository is definitely not a good idea --> We need to get rid of this commit
  • Fixing Bug --> What is this commit for.  We need to reword the commit message
  • Refactoring --> The refactored file and its test case should be one single commit.  We should not have separate commits for them.  We need to merge these two commits.
GIT gives us a second chance!  Its not too late yet.  We can fix our mistake.

Please note that we have not yet pushed those commits to the remote repository.  Rewrite history should be used only in this case.  If we have already pushed our changes to the remote repository this feature should be avoided!

The fixup:

There are multiple ways of doing this but I will show the easiest.  To rewrite history use the following command

This command tells git to do an interactive rebase. The "remotes/trunk" tells git that it has to do the rebase on the commit that is currently the HEAD on the remote repository. Basically its the commit that was last pushed to the remote repository. In this example its the commit "88e33ddc9c7b139bb89cbd206e838fe453978e2f" (SOMEPROJECT-1| User is able to register on the site - Deep). With this explanation its obvious that we could also rewrite the above command as

This command will open up your favorite text editor. The data in the text editor should look something like this.

It shows the list of commits that will be rebased. It sows some pretty informative comment below that. It tells us to use:
  • p, pick --> To pick the commit or use the commit as is.
  • r, reword --> To reword the commit message
  • e, edit --> To edit the commit.  We will look at this in the next post
  • s, squash --> It practically means to squash!  It merges the commit with the previous commit
  • f, fixup --> This one is just like squash but discards the current commits log message.
  • It also tell us that if we remove any commit line, that commit will be removed.

Pretty neat!  We want to do the following
  • Remove the commit be6628d SOMEPROJECT-2| Adding the administrator password to the repository - Deep
  • Reword the commit b63e0c3 Fixing bug 
  • Merge the commits 62c2f81 SOMEPROJECT-3| Refactoring the file to improve performance - Deep and 95a929a SOMEPROJECT-3| Test case to prove that performance has actually improved - Deep
Lets edit the commit lines and now they should look like this

Note that we have removed the commit line for "be6628d SOMEPROJECT-2| Adding the administrator password to the repository - Deep". We have informed GIT that we want to reword a commit and squash two commits.

Save and quit from the text editor

After that, GIT will open up your favorite text editor.  This time should have information like this

Lets edit the commit message like this


Save and quit the editor. GIT will open up the text editor again. This time it will look like this

Look at the beautiful message GIT has given us. It tells us, this is a combination of 2 commits and give us the two messages. If we want we can edit the messages. But for the sake of this example lets keep the messages as is. Save and quite the editor. It prints some messages like this

Now, lets take a look at how our local commits look like after the rebase.

From the log its evident that we have successfully

  • Removed the commit be6628d SOMEPROJECT-2| Adding the administrator password to the repository - Deep
  • Reword the commit b63e0c3 Fixing bug to SOMEPROJECT-2| Fixing the programing error because of which a deadlocak situation could arrise - Deep
  • Merge the commits 62c2f81 SOMEPROJECT-3| Refactoring the file to improve performance - Deep and 95a929a SOMEPROJECT-3| Test case to prove that performance has actually improved - Deep.  The new merged commit id is 555cb8f
Its needless to say that GIT has an awesome support to rewrite history!  

Still not using GIT?  Do no waste any time.  Go GIT it!
Have some Fun!