Friday, January 28, 2011

How to rewrite history with GIT - Part - 2

This is the second post in the series of two posts to show how easily we can re-write history using GIT.  In the previous post we saw
  • How to change the message of one of the previous commits
  • Removing a commit
  • Merging or squashing two or more commits.  
In this post we are going to look at
  • How to re-order the commits
  • Editing the previous commit to include/remove files.  
Lets not waste any more time.  Lets look at how is it done!

How do they do it?

As in the previous post, lets start by doing some mistakes, by making a few erroneous local commits.

The current situation 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.

Lets say I had to write a script to insert dummy records in the user table

Committing the new file

After committing this file, I remembered that, the script to create the USER table itself is not yet checked in. Lets create the create user table script and check it in.

Committing the create user script.

Heres a problem, we have the insert user script committed before the create user table script.

The table should exist before we can insert data into it right! We will have to reorder the two commits. But before we fix this issue, lets make some more mistakes.

Lets say, we want to create the role table.

Committing the file.

Note that I have not assigned any primary key to the role table. This again is a problem. We should have a primary key associated any table. Lets do one final commit and insert a ROLE_ADMIN in the role table.

Committing the file.

I realize that we have made enough mistakes, its time to correct them.

Looking at what we have so far.

We have made 4 local commits which have not been pushed to the remote repository. Before we push them to the remote repository we would like to do the following changes
  • The insert user script commit (fded92599c04f70c17a40695d1dfd2c54fa5efe2) should come after the create user table script commit (7f4288ea82a69001f3872630ab05672a54730558).
  • There is no primary key assigned to the role table in the create role table commit (d1f9d2df72ff3470d971a6d7f00b9d35caf59ee6).
To fix the second point, we could make another commit to alter the role table and add a primary key.  But lets edit the commit and make the "id" column as the primary key in the create table statement itself.  This way the create role script will be complete and bug free.

Lets use the command, git rebase -i remotes/trunk (we learned in the previous post) to fix our mistakes.

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:

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 re-write the above command as

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

We have seen this message in the previous post. Short recap of the options we have:
  • p, pick -> To pick the commit or use the commit as is.  We used this in the previous post.
  • r, reword -> To reword the commit message.  We used this in the previous post.
  • e, edit -> To edit the commit.  We want to edit the create role table script to add the primary key.  Hence we will edit the commit SOMEPROJECT-2| The create role table script to create the role table - Deep (d1f9d2d)
  • s, squash --> It practically means to squash!  It merges the commit with the previous commit.  Saw that in the previous post
  • 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.
To re-order the commits we simply need to change the order of the lines in this text message.

Summarizing what we want to do:
  • Edit the commit d1f9d2d SOMEPROJECT-2| The create role table script to create the role table - Deep
  • Reorder the commits fded925 SOMEPROJECT-1| The create user script to create the batman user - Deep and 7f4288e SOMEPROJECT-1| The create user table script to create the user table - Deep
  • Pick the commits 8745c7b SOMEPROJECT-2| The insert roles script to insert the role values - Deep
Lets edit the commit lines and now they should look like this

Note that we have changed the order of create user table and insert user script commits and we are editing the create role script commit.

Save and quit from the text editor.  After this, GIT will stop at the create role table commit and show some output like this

It clearly tells us that we can now amend the commits using the git commit --ament command and when we are satisfied we can do a git rebase --continue. Lets see what git log shows us

It shows that
  • Currently the top most commit i.e. the HEAD commit is SOMEPROJECT-2| The create role table script to create the role table - Deep
  • The commit SOMEPROJECT-2| The insert roles script to insert the role values - Deep is missing
Where did the SOMEPROJECT-2| The insert roles script to insert the role values - Deep commit go?

Relax! do not worry. We are in the middle of a rebase and we had asked GIT that we want to edit the SOMEPROJECT-2| The create role table script to create the role table - Deep commit. We wanted to add the primary key to the role table. Hence, while doing the interactive rebase, git has stopped at the desired commit and given us a chance to edit/amend it.

We can do the required changes for adding the primary key to the role table and then amend the commit.  After this if we continue our rebase we will get back the SOMEPROJECT-2| The insert roles script to insert the role values - Deep commit!  How awesome is that!
Lets edit createRoleTable.sql file 

Lets amend the commit.

While amending the commit git will open up your favorite text editor and lets you edit the commit message. We do not want to edit any message, lets just save and quit the text editor.

If you do a git show now you will see that the createRoleTable.sql has been successfully updated in the commit SOMEPROJECT-2| The create role table script to create the role table - Deep

Lets continue to rebase and get back the last commit.

This command finishes the interactive rebase and we have achieved the desired result. Doing a git log now, will show that we have successfully re-ordered the commit as well.

Rewriting history is one of the best features offered by GIT! The elegance with which git achieves this is amazing!

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