How to Work Efficiently With Git ?

Git is a tool, and like any tool, there is a big difference between using it and using it properly. I will describe in this article the workflow I’m using in my day-to-day developer’s job.

Starting to Work on a Ticket

When I start working on a Bug, on a User Story, on a Task or on whatever, I always start by checkout a branch containing my name, a short description of the issue and ticket number. It looks like :

git checkout -b tan/fix-event-crash-MIC-1831 origin/master

Saving my work

After a few minutes or a few hours, depending on the complexity of the task, I end up to commit my modifications. I rarely use command line for commit, but use git gui instead:

git gui

And I systematically push my branch, also with git gui push button. This has 2 advantages:

  • If I screw up my repos, I can still reset on the last time I pushed by branch.
  • If my computer crash for any reason, I have a backup of my work.

I also use gitk a lot to see current status of my branch:

gitk

Here I can see that I’m based on master, thanks to the remote/origin/master label. I have 3 commits in my branch and my branch is currently pushed, thanks to the remotes/origin/tan/feat2 label being the same as local tan/feat2. So my work is saved.

I really commit/push A LOT. I push several times a day, and sometime up to several times per minutes for very short modifications.

Retrieving Others’ Work

I regularly rebase my branch on current master to retrieve other’s work and address conflicts as soon as possible.

git fetch
git rebase origin/master

Note that I rebase, and not merge master into my branch. I want my history to be linear, and the only merge points that appear in history are merged of feature branches into the master branch.

Always Push at the End of the Day

Whatever the status of my code, I always commit my work at the end of the day and push it. I never know what can happen. If I’m in a draft state, I just put everything in a commit and prefix commit message with “WIP”:

Modifying the Yesterday’s WIP Commit

After a good night, I wake up and wonder what the hell I was doing yesterday. I clean up my mess and want to do a clean commit. But I commit an ugly “WIP” commit yesterday. Here comes the awesome “Amend Last Commit” git gui checkbox:

This checkbox let you modify the last commit, rewrite commit message, stage/unstage files etc… So I really have no reasons to not commit a WIP work.

Interactive Rebase

I often do intermediates WIP commit, to explore a path until it leads to something concrete. I also frequently do small dumb fixes or typo correction that should go on older commit. To avoid having a lot of intermediate commits that goes back and forth, I practice interactive rebase. Interactive rebase is a wide topic. You can have a look here to have more details.

In the example below, while fixing a crash, I fix a typo in documentation I previously wrote. To indicate it, I prefix the commit message with an f for fixup. I would like this commit to be squashed with the 2nd add doc commit, and then I would like to squash the WIP commits with the fix feat 1 commit.

To do so, I do an interactive rebase:

git rebase -i origin/master

And go from this:

to this:

Now my branch is clean, I can publish the merge request and ask for review.

Conclusion

This workflow is well adapted to Trunk based development, but can also be used for any git branching models. The most important points are:

  • Rebase branch on master instead of merging master in branch.
  • Always push branches, even draft and work in progress branches.
  • Learn interactive rebase.

(Bonus) Retrieving Temporary Work of Others

Ideally, development branches should not live more than a few days, or even a few hours, so you should never have to use the work of other developers before it has been integrated in master. But in complex monolithic legacy projects, integration may take days or weeks… In such a case, your work may depend on someone else work that is still not integrated. There are many ways to do this: Rebasing branch on each other, cherry-picking commits, merging branches, … I will describe the method I use.

Assuming I am in this situation. I already have some commit, starting from master:

But I some bob/fix-db branch to continue my work. This branch is in the integration pipeline, but won’t be available before next week. In this case, what I do is:

# ensure everything is up to date
git fetch

# save my work. 
# If I screw up, I can still reset to what I just pushed
git push origin HEAD:tan/feat2

# get an overview of my work, and keep it on the side
gitk&

# now serous things begins. Reset my branch to common trunk
git reset --hard origin/master

# I start by merging the branch I need
# --no-ff force git to create an explicite merge commit
# and not flatten the history in case of fast forward.
git merge origin/bob/fix-db --no-ff
# Maybe I need several branches to continue my work. 
# I do this for each branches.
git merge origin/ben/fix-build --no-ff

# now, I retrieve my commits from the opened gitk
git cherry-pick ...
git cherry-pick ...
git cherry-pick ...
# using range git cherry-pick sha1...sha2

# And finally, check my branch looks good
gitk

History should look like this:

I can see that:

  • My branch tan/feat2 start from master
  • It first merges the bob/fix-db. I see this thanks to commit Merge branch 'bob/fix-db' into tan/feat2.
  • The label remote/origin/bob/fix-db is just before merge point, so I’m up-to-date with Bob’s work.
  • I’ve added 4 commits on top of Bob’s work.

The main advantage of merging dependencies and cherry-picking your own commits, is that you and your colleagues can clearly see in history what is your own work and which branches your work depend on.

There is also another big advantage. Imagine the branch bob/fix-db broke something during integration and Bob had to fix its branch. By cherry-pick its commits or by basing your branch on its branch, you cannot see this. With the merge technique, you can directly see it:

git fetch
gitk

You notice that the remote/origin/bob/fix-db has disappeared, indicating bob did something to its branch. In this case, just repeat the procedure described previously.

This procedure can also handle case when pbr/fix-db has finally been integrated in master. In this case, the git merge origin/pbr/fix-db will tell you this branch is already merge and there are nothing to merge.

Ideally, you should repeat this procedure until all dependent branches has been merged, and send your merge request only when you have only your modifications in your branch.

As said at the beginning, this technique is really a workaround to handle very long integration pipeline. If your work depend on other’s one, it is always better to wait their work to be integrated than doing high-flying gitology.

Home


Similar topics

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s