On the 31st of May 2019, a couple of colleagues and I travelled to Utrecht to attend a Fronteers Git workshop; Getting to grips with Git. At VI company we use Git for version control, use a form of Git Flow and use Azure DevOps as our CI/CD tool. Some of the group are more experienced with Git, but all wanted to learn more about what Git had to offer.
The workshop leader was Pauline Vos and we were her worst nightmare… Reason being that she is more accustomed to using Git in Mac OS and PHP and we are Windows and .NET users. Nevertheless, this worked out just fine after some adjusting and we learned a lot. In this article, I will discuss the three main takeaways I had and share three fun facts.
1) Make atomic commits
Atomic commits help you keep a clear and workable Git history. This helps pull requests and code reviews to be smaller and well portioned. Also, it’s a clear basis for rewriting history and Bisect. Of course, you can do both without clean commits but then it’s quite hard because you’re working with large chunks of committed code.
Example; Sometimes when I’m developing a feature, I’m really in the flow. Creating unit tests, repositories, services, models and whatnot. Hours pass and I do the first commit… Well, that is something I can improve. To comply with atomic, you must follow 3 ground rules.
- 1 feature/fix per commit
- The solution should work and be tested
- A clear and concise commit message which features a title around 50 chars
To improve this, I should do a commit for my first non-dividable unit of work. For example; a set of models or a unit test or the repository. It seems lame to commit each part individually, but it helps later. If you want to take out a certain change or cherry-pick some part of the feature, then it’s possible.
2) Rewriting history
Rewriting history is connected to an internal debate we have regarding force push rights to alter the Git history. To be honest my expectations from the workshop were that afterwards we could settle the score. But with all things in life, the solution depends on your arguments and context.
In general, at VI company we don’t rewrite history in Git. This is because we treat the history of a reflection on what happened. Still, this can make your Git history very cluttered. Merging an update from a released branch into your feature branch creates these not very informative ‘merge’ commits. What I learned is that it’s better to use rebase in such situations. This only places my feature at the front of the branch we would otherwise merge on it.
How does force push rights come in to play? Well if you would have pushed the feature branch to the repo and rebased the local branch, the commit hash would be different between what you have locally and on the remote, because you’ve placed the feature in another place in time. This isn’t possible to push, and you must have force push rights. This right is a great responsibility because you can remove the code from the origin without a trace. This is something Pauline also warned about and others at VI are also very cautious about.
The Monday after the workshop I had a PR that Johan was reviewing. He advised me to interactive rebase my commits so I could squash one commit in another. Also, I rebased my changeset after the latest version of master. Both actions resulted in a better commit history. For now, I like to rebase my feature branches.
Do you ever have issues popping up and you don’t know from which commit the issue originated? Of course, we test our code and try to validate our changes often but sometimes issue that worked a month ago are now broken… If so, bisect is a tool to use. If you would read the description of Bisect in the Git manual it sounds like a silver bullet;
Use binary search to find the commit that introduced a bug
Still, one prerequisite for using git bisect effectively is writing atomic commits. The general idea is that you tell Git bisect which commit was good and which was bad. Then Git picks a commit by Binary search and checks it out. Now comes the part where you must decide if this commit also contains the issue. Telling this to Git will check out a remaining commit. If all commits are worked through, Git will tell you which commit was the first bad one.
Below an example;
- Starting to bisect;
git bisect start
- Ten commits ago I knew the feature still worked, so I mark this as the good one;
git bisect good head~10
- The head is set the bad one because now it’s broken;
git bisect bad head
- Bisecting starts on a commit ec86c80e71511d26e7f75fdd5b0b67ada5764292
- Now I can test this commit and see if the feature still works;
git bisect good
- Git bisect selects another commit and I test the feature and notice it doesn’t it work. This commit gets labelled as bad;
git bisect bad
- I do this process of labelling the selected commits as bad or good until git bisect shows me the bad commit.
- I can reset bisect with;
git bisect reset
- Now I can view which changes were done in this commit and find a solution from those changes
- Some companies use Gitmoji as a standard for their commit messages to identify what kind of commit it is. For instance, a caterpillar icon is used for a bug or a painter pallet for design changes.
- Pauline told us she was attacked by a baboon once, but never explained why.
- To separate a commit in multiple smaller chunks, you must use the git hunks option. Why they ever named it this way and not chunks, is very odd.