{"id":129,"date":"2024-12-04T09:00:00","date_gmt":"2024-12-03T22:00:00","guid":{"rendered":"https:\/\/jm.armijo.au\/dev\/?p=129"},"modified":"2024-12-04T07:36:28","modified_gmt":"2024-12-03T20:36:28","slug":"clean-commits-with-git","status":"publish","type":"post","link":"https:\/\/jm.armijo.au\/dev\/blog\/2024\/12\/04\/clean-commits-with-git\/","title":{"rendered":"Clean Commits With Git"},"content":{"rendered":"\n<p>If you have ever inspected the Git history of some of your projects (doing <code>git log<\/code>) I am sure that you may have encountered something like this in your <code>main<\/code> branch:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">f1e2d3a fix\na9b8c7d <span class=\"hljs-keyword\">try<\/span> again\n<span class=\"hljs-number\">0<\/span>a1b2c3 Update\nd4e5f6g stuff\n<span class=\"hljs-number\">7<\/span>h8i9j0 oooops\nk1l2m3n <span class=\"hljs-keyword\">Final<\/span> <span class=\"hljs-keyword\">final<\/span> fix\no5p6q7r more changes\ns8t9u0v Asdf\nw1x2y3z WIP\nz4x3c2v This should work<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This looks really unprofessional, but the problem goes beyond aesthetics. Having poor descriptions like this is a problem that is more common than it should be. I believe that there are two main reasons why developers tend to do this:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A lack of understanding of the importance of clear, meaningful commits often leads people to skip them, not realising the value outweighs the effort.<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A lack of knowledge to improve their commits, even if they wanted to, as discussed here: <a href=\"https:\/\/jm.armijo.au\/dev\/blog\/2024\/10\/30\/the-impact-of-mastering-our-day-to-day-tools\/\">https:\/\/jm.armijo.au\/dev\/blog\/2024\/10\/30\/the-impact-of-mastering-our-day-to-day-tools\/<\/a><\/li>\n<\/ul>\n\n\n\n<p>In this article, I will explore what defines a good commit, why we should care about them, and how we can write good commits with the help of Git.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Good commits<\/h3>\n\n\n\n<p>A good commit requires a clear message, and that the code can run. Those things seem obvious, but they are actually not. Let&#8217;s discuss them in detail.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Commit Messages<\/h4>\n\n\n\n<p>In my opinion, a great commit starts with a great commit message. To qualify as such, it should have at least these two things:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A reference to the task that prompted the change (e.g., Jira or Trello ticket).<\/li>\n\n\n\n<li>A clear, meaningful description of the changes made.<\/li>\n<\/ul>\n\n\n\n<p>When someone makes a commit, they usually have all the context about the task, so writing the obvious may seem like a waste of time, but it is not. A clear description allows people to understand what was changed without having to read the code, and a reference to a well maintained ticket to allow them to quickly find relevant documentation about the change, like architectural or UI designs, decision records, etc.<\/p>\n\n\n\n<p>When you take the time to write good commits you are saying:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><em>To whoever reads this, I&#8217;ve done my best so you know what changes are in this commit and why I made these changes.<\/em><\/p>\n<\/blockquote>\n\n\n\n<p>When you don&#8217;t write a good commit, you say this instead:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><em>To whoever reads this, I don&#8217;t care if you understand this or not. I just want to ship my code. Good luck.<\/em><\/p>\n<\/blockquote>\n\n\n\n<p>Personally, I like to use a format where the first line has a summary of the change, and the following lines provide more details about what really changed. For example, instead of <code>This should work<\/code>, we could write something like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">JM<span class=\"hljs-number\">-1234<\/span>: Add user profile customisation options\n- Add <span class=\"hljs-string\">`ProfileController`<\/span>\n- Add routes <span class=\"hljs-keyword\">for<\/span> handling profile updates.<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That said, the exact format is not important, as long as you or your company have a standard and you consistently follow it.<\/p>\n\n\n\n<p>I have been in situations where the code base is so old that everyone who worked on that project already left the company, so there is nobody to ask. I&#8217;ve also experience cases when I need to find that commit now, but the person who wrote the code is in another timezone, probably partying or sleeping, and the task does not justify paging them.<\/p>\n\n\n\n<p>So trust me, there is always someone that will have do get their hands dirty. This may even be your future self, and yeah, I&#8217;ve also had to look for commits that I have made with no clue of what I&#8217;m looking for until I read the message that gives me the answer.<\/p>\n\n\n\n<p>The same reason applies for using a fixed formatting for our commit messages. We can write simple scripts to help us in our quest to find a commit as long as they have useful information to search for and have a structured format that makes parsing easier.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Running Commits<\/h4>\n\n\n\n<p>Last but not least, I believe that every commit in <code>main<\/code> should contain a valid copy of the software. This means that if we check out that commit we should be able to successfully run the software and all tests should pass.<\/p>\n\n\n\n<p>The reason for this is not always straightforward, after all, what matters is that the last commit in the build works, right? Well, it depends on who you ask.<\/p>\n\n\n\n<p>If your sole goal is to ship code, yeah, maybe this would be true, but as we saw earlier we also want everyone to be able to inspect the history of changes, and that includes finding bugs.<\/p>\n\n\n\n<p>Yeah, I have often gone through the commit history trying to identify the commit that introduced a tricky bug. It&#8217;s kind of simple, you just check out a commit, run some code to reproduce the failure (usually a test), and voil\u00e0! Except that if the commit temporarily broke the code then it&#8217;s no longer possible to test if the bug I was looking for was present in this commit or not.<\/p>\n\n\n\n<p>Just to be clear, I&#8217;m not saying that commits will be bug free. We do our best to avoid bugs, but they get in regardless. What I am talking about is when we have a branch where we break something and then we fix it in the next commit, but the bad and the good commit are shown in the pull request and land as separate commits in the <code>main<\/code> branch.<\/p>\n\n\n\n<p>Some developers I have spoken to justify merging their list of changes as a bookkeeping, to record the history of their changes. But trust me, in 17 years of writing and debugging code, I have never ever had to know in what order a change was made, nor seen or heard of anyone who has benefited from having a detailed list of commits for a single PR.<\/p>\n\n\n\n<p>So, should you only commit when you know your changes work? Not really. Please keep committing as frequently as you can, that&#8217;s actually good! We&#8217;ll solve this dilemma with Git.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Git To The Rescue<\/h2>\n\n\n\n<p>Git provides a way to combine two or more commits into one. So, you can commit as many times as you want, and when your code works, you can combine all these commits into a single commit.<\/p>\n\n\n\n<p>Good commits in any intermediate commits are useful to you, but in a combined commit they are useful to your fellows developers. There&#8217;s no need to specify that you tried something and then changed your mind. What really matters is the final result, as this is what you will merge (when your PR gets approved).<\/p>\n\n\n\n<p>Squashing commits is very simple. Let&#8217;s pretend that we want to squash the commits in our example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">$ git log --oneline <span class=\"hljs-comment\"># top commit is newest<\/span>\nf1e2d3a fix\na9b8c7d <span class=\"hljs-keyword\">try<\/span> again\n...\ns8t9u0v Asdf\nw1x2y3z WIP\nz4x3c2v This should work<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You can execute this command to squash all these commits:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">git rebase -i z4x3c2v^<\/code><\/span><\/pre>\n\n\n<p>So, we use the <code>rebase<\/code> command to squash commits. We specify the <code>-i<\/code> flag to go to interactive mode (you&#8217;ll see what this is in a minute), and then we specify the first commit that we want to squash, plus the <code>^<\/code> character to specify that this is inclusive of this commit.<\/p>\n\n\n\n<p>This will open your editor for you to tell Git what you want to do with those commits (this is why this is interactive). You will ses something like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">pick z4x3c2v This should work\npick w1x2y3z WIP\npick s8t9u0v Asdf\n...\npick a9b8c7d <span class=\"hljs-keyword\">try<\/span> again\npick f1e2d3a fix<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Note two things: commits are shown in reverse order, and before every commit there is a <code>pick<\/code> word, which means that Git will use this commit normally. If you save this file and exit, everything will stay the same.<\/p>\n\n\n\n<p>Additionally, below the commit messages, there is a list of all valid options that you can use, with a description of what they do. In this example we&#8217;ll just use one of them.<\/p>\n\n\n\n<p>To squash your commits you need to change the <code>pick<\/code> word to <code>squash<\/code> in all but the first commit, like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">pick z4x3c2v This should work\nsquash w1x2y3z WIP\nsquash s8t9u0v Asdf\n...\nsquash a9b8c7d <span class=\"hljs-keyword\">try<\/span> again\nsquash f1e2d3a fix<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Then you can save the file, and Git will ask you to edit the commit message. It will include all existing messages so you can use them if needed, or you can just discard them and write an entirely new commit message. When you save, this new commit will replace all the commits that you specified, leaving a clear history before you send your changes for review.<\/p>\n\n\n\n<p>Of course, for this to work you need to test your code. Execute the linter, unit tests, and any other checks you can before you push your changes. You can do this using a pre-commit hook, so you don&#8217;t even have to remember to do it.<\/p>\n\n\n\n<p>GitHub allows us to squash our commits when merging. This should not be taken as permission to avoid cleaning our work before raising a PR, but as an opportunity to do an extra cleanup due to review comments. If we clean up our commit history before raising a PR we help prevent the consequences of an &#8220;oops, I forgot to squash when merging&#8221;; and it also shows respect for our teammates.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<p>Clear, meaningful and functional commits are more than just good practice, they&#8217;re a way to respect your team, yourself, and the codebase. We can foster collaboration and make debugging far less painful if we follow these simple steps.<\/p>\n\n\n\n<p>Good commits may take a little extra effort, but the payoff in efficiency and professionalism is well worth it. Let\u2019s aim for a cleaner, more maintainable Git history, one commit at a time!<\/p>\n\n\n\n<p>Cheers!<br>JM<\/p>\n\n\n\n<p>Share if you find this content useful, and Follow me on <a href=\"http:\/\/www.linkedin.com\/comm\/mynetwork\/discovery-see-all?usecase=PEOPLE_FOLLOWS&amp;followMember=jmarmijo\">LinkedIn<\/a> to be notified of new articles.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you have ever inspected the Git history of some of your projects (doing git log) I am sure that you may have encountered something like this in your main branch: This looks really unprofessional, but the problem goes beyond aesthetics. Having poor descriptions like this is a problem that is more common than it [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":144,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[13,10,14,6,11],"class_list":["post-129","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tools","tag-code-review","tag-git","tag-pull-request","tag-tools","tag-version-control"],"_links":{"self":[{"href":"https:\/\/jm.armijo.au\/dev\/wp-json\/wp\/v2\/posts\/129","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jm.armijo.au\/dev\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jm.armijo.au\/dev\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jm.armijo.au\/dev\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/jm.armijo.au\/dev\/wp-json\/wp\/v2\/comments?post=129"}],"version-history":[{"count":19,"href":"https:\/\/jm.armijo.au\/dev\/wp-json\/wp\/v2\/posts\/129\/revisions"}],"predecessor-version":[{"id":150,"href":"https:\/\/jm.armijo.au\/dev\/wp-json\/wp\/v2\/posts\/129\/revisions\/150"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/jm.armijo.au\/dev\/wp-json\/wp\/v2\/media\/144"}],"wp:attachment":[{"href":"https:\/\/jm.armijo.au\/dev\/wp-json\/wp\/v2\/media?parent=129"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jm.armijo.au\/dev\/wp-json\/wp\/v2\/categories?post=129"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jm.armijo.au\/dev\/wp-json\/wp\/v2\/tags?post=129"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}