I would like to share a workflow that I recently got used to when using git.

When using git, there are a few ideas that people always talk about.

  • Commit often
  • Commit should be logical working chunk

This workflow really embraces these ideas.

Usually my day will start with me writing code for an hour or two. During this period, I will be heavily committing my code, usually after I finish a logical part.

Note that committing is not pushing. These code that I just wrote are not logical working commit. They are there to help me if I accidentally screw up something and I have something to go back to.

Usually after working for a few hours, my history looks something like this.

* cffb62d - before dinner
* bae28b0 - qoddiuqhjdkqwhnu
* d2256b9 - bla
* de58766 - fix timestamp issue
* d0ba650 - temp
* 4dedd9e - its working !!!
* 3bba255 - .... (github/master)

Yes, I usually just type random commit messages because it is really meant to be temporary.

At this stage, I will usually begin my resetting and re-committing. We will always reset to the last upstream commit, in this case it will be 3bba255. This will allow me to unstage everything and start commiting logical chunks.

* f3f4009 - feat(search): added search endpoint
* 5d8bab3 - upgrade(utils): add more methods to utils.
* 79e33d0 - refactor(base): refactor some base method
* 3bba255 - .... (github/master)

If all is good and feat/search can be pushed upstream, my job is done here. Instead of committing the entire feature as a single commit, the refactoring and utilities are committed as a different commit. One of the advantages when doing this is the ability to push them upstream without pushing feat/search.

However, this feature is rather big, and it is going to take me another day to finish it. Also, suppose that the utils and base is not really tested, so I am not ready to push.

Day 2 comes and I start working on things and added the following commits

* 9e47004 - doiqjdwq
* 4546c6a - dqwduwh
* 878eafa - whatever
* 89328ac - temp
* c904eb0 - stupid bug
* f3f4009 - feat(search): added search endpoint
* 5d8bab3 - upgrade(utils): add more methods to utils.
* 79e33d0 - refactor(base): refactor some base method
* 3bba255 - .... (github/master)

After I am done with the day, instead of resetting to 3bba255, which is the previous upstream, I will reset to the last commit that I have committed “properly”.

* 5777712 - fix(feat/search):
* dc7918a - test(base): add test cases for base
* 27170d8 - regression(base): fix bug due to refactor
* 24b1fd8 - fix(utils): fix some bug
* 7a552c0 - fix(random_bug): fix random non-critical bug
* f3f4009 - add(feat/search): added feat/search, endpoint is working
* 5d8bab3 - upgrade(utils): add more methods to utils.
* 79e33d0 - refactor(base): refactor some base method
* 3bba255 - .... (github/master)

This is usually good enough to push but we can do better. From the master perspective, certain bug fixes to commits that are not pushed to master shouldn’t really be visible, for example 24b1fd8. Also, in the middle of building this feature, we have fix a random non-critical bug, perhaps we can somehow push that first ?

So rebase to the rescue.

Let’s start by performing a rebase - rebase -i 3bba255.

You will see something like this pop up in your editor.

pick 79e33d0 refactor(base): refactor some base method
pick 5d8bab3 upgrade(utils): add more methods to utils.
pick f3f4009 add(feat/search): added feat/search, endpoint is working
pick 7a552c0 fix(random_bug): fix random non-critical bug
pick 24b1fd8 fix(utils): fix some bug
pick 27170d8 regression(base): fix bug due to refactor
pick dc7918a test(base): add test cases for base
pick 5777712 fix(feat/search):

# Rebase 3bba255..5777712 onto 3bba255 (7 command(s))
# ... wall of text removed ...

This may look extremely confusing but once you know what it can do, it is quite amazing.

So we need to do a few things here.

We will be rearranging how we intend to commit; fixes first, then non-feature commits like utils and base classes, then then finally the feature.

We will also be squashing the fixes that we introduce in this 2 days into their feature commit, since master do not need to know about it.

So this is what we will do.

pick 7a552c0 fix(random_bug): fix random non-critical bug
pick 79e33d0 refactor(base): refactor some base method
squash 27170d8 regression(base): fix bug due to refactor
squash dc7918a test(base): add test cases for base
pick 5d8bab3 upgrade(utils): add more methods to utils.
squash 24b1fd8 fix(utils): fix some bug
pick f3f4009 feat(search): added search endpoint
squash 5777712 fix(feat/search):

Do take note that the top is the first commit that will be committed.

After this, your editor will open, and you will be able to enter new commit messages for those that you are squashing.

And finally, we have

* 49b77f9 - feat(search): added search endpoint
* 8996390 - upgrade(utils): add some methods to utils
* 211c849 - refactor(base): refactor some base methods
* 5fdeecf - fix(random_bug): fix random non-critical bug
* 3bba255 - .... (github/master)

Of course, I haven’t show you any code or teach you how to create logical commits. The rule of thumb is to ensure that every commit works, and every commit can be cherry-picked, even if you don’t use it.

One last thing to note when doing this. Do not rebase what is pushed upstreams, or you will have a bad time.