Using Git commit message templates to write better commit messages
gist.github.com
gist.github.com
I'm a big fan of Conventional Commits[1], so my commit template reminds me of the types and provides an actual template to describe my change:
<type>(optional scope):
# type: build ci chore docs feat fix perf refactor revert style test
Problem:
Solution:
Testing:
Issue:
Where Problem describes why this change is needed, Solution is what this change delivers, and Testing is how this change was validated. Issue is the JIRA/Github issue related to this change.EDIT: The Problem/Solution/Testing/Issue is not part of Conventional Commits, I added those to give structure to the body.
Yes/perhaps/maybe, but it's nice to (a) have it in front of you without having to change programs or context, and/or (b) have it summarized, as the ticket may have 'extraneous' stuff like a bunch of back-and-forth with the user/client/tester/etc.
What
Why
How
Related
The only “bad” thing is that I feel some of the <type> standards don’t fit very well for certain cases that are not exactly distributable software. (In my case, devops, committing to a TF project we own: mmmm is this a “chore”, a “feat” etc)
I think that a commit should always be as small as possible and have a descriptive, meaningful message. It is therefore pretty common that I end up with tens of commits when working on a simple feature. Which of these commits should be labelled as "feat" when the feature is added by a group of commits rather than a specific one, and that none of them really adds any feature? Also, a bug can be fixed by many commits. Which of these commits should be labeled as "fix"? We can't label all of them as fixes as cherry-picking only one of them wouldn't fix anything, which means the commit message would be misleading. The only solution left is to create one commit per bug fix or per feature.
Moreover, this information can (and should) be carried by the branch name already, as you'd usually branch off from your main branch to either fix a bug or develop a new feature. All you have to do is to follow a naming convention for your branch like "feature/my-awesome-feature" or "bugfix/fix-this-annoying-bug".
The only benefit I see with using conventional commits is generating changelogs automatically from your git history.
Why can't each small change be labeled "feat"? A feature does not need to be contained in a single change as a "big bang" commit. I make multiple "feat" changes to deliver a feature.
I follow trunk-based development[1], so branches are many, transient, and disposable (and sometimes not even present if it's a small change I'm pushing straight to `main`).
The advantage of Conventional Commits is that I can go to my trunk (`main`), and scroll through the list of commits and see their purpose at a glance:
feat: ...
feat: ...
build: ...
fix: ...
feat: ...
chore: ...
I can immediately tell which changes were related to fixing the build, or a bug, or supported feature development.I realize that this isn't applicable to all codebases, but when troubleshooting why a bug happens, or was intended to work, I find it a lot easier to look at a squashed commit than at the individual commits that went into a pull request. It does make `git bisect` less specific (and, to my shame, I've never used it), but having an overall commit message + pull request that points at a Jira ticket (or similar) helps identify whether something was intended behavior or not, and I've never felt the need to look deeper at whether part of it was a refactor/chore/feature. (If I had, I could look at the PR branch, presumably.)
In contrast, I've often had a stream of a 12-20 commits with tiny atomic (and often terrible) messages like "bugfix", "add test", "fix test", "fix linting", which I then try to rebase into some _coherent_ messaging of the time you espouse, where linting + typo fixes are rearranged and squashed on top of the commits that added them, and tests + features are added more closely together. It's very satisfying to do this, but in retrospect I've always felt like the time I spent doing that has been wasted --- it's hard, and often involves many iterations of conflict resolution when reordering commits, all for something that I or my team will squash anyway. The benefit gained (more readable commits of temporary value) never ever seems worth the time (Multiple hours) put into making them that way.
Me too, actually I hate conventional commits with a pattern, because I have never seen a "conventional commit message" that actually does a good job. I've recently even written a short article on that subject (https://news.ycombinator.com/item?id=29924976) because it is so annoying to me that people think writing a "conventional commit message" alone results in better quality (hint: it does not).
I keep it simple - branches are short-lived. I don't even like feature branches, I break features into a series of changes and push each change to `main`, incrementally delivering all the changes needed to make the feature useful while keeping `main` healthy.
More details at: https://trunkbaseddevelopment.com/
git config --global commit.verbose true
https://tekin.co.uk/2020/03/git-commit-verbose-mode“Progress”
Change the submit button color to green
Bump library XYZ to 1.2.3
Refactor and simplify the cart repositoryThen you'll investigate further and find the commit that made the thing, read your commit message above and say, "Okay, why did I change this thing? I'm guessing it solved a problem at a time, but I don't know what the problem was."
You'll decide: "I'm just going to pull this out and replace it with this easier thing".
Then everything will work except for one thing, and you'll end up wasting days trying to sort out a weird corner case. Part way through that you'll come up with a solution that is more complicated then the easy refactor you tried to make and at some point you'll think, "Oh, that's why that was like that".
You could have saved 3 days of work by just spending an extra 30 seconds to write the "Why" in your commit message. Your commit already includes the code changes (the "What"), so put a one/two sentence "Why" in your commit message.
If there is a reason for it being done 'this way' that is not obvious then there should be a comment in the code explaining the thought process.
A commit won't really help you as much there as future refactoring might obscure the original commit that added the code.
That being said, if there's some complexity in what is being solved I will usually add a very quick description of it in the commit message "Used XYZ algorithm to implement ABC due to it being faster/cleaner. See: ticket/wiki/documentation".
Totally agree. Unofortunately, a descriptive git commit the best you can require without starting a religious war in the team.
You won't believe how many developers zealously subscribe to the "no comments needed - good code is self-explanatory" crap. Or throw in the "comments will quickly become outdated" excuse.
To any technical leaders reading this comment, I encourage you to choose to care about this :)
Here's the post I always share on this: https://tbaggery.com/2008/04/19/a-note-about-git-commit-mess...
2. Educate and train ppl to include commit messages when they do code reviews
or 3. give up and move on because your org doesn't care.
Because this is a culture (not code) smell and changing culture is hard and needs support from the top.
When those branches are attempted to be merged they'll fail due to having bad commits and require fixing.
Strictly speaking, that's what git stash [1] is for. Not so ideal if you want to push your WIP changes to Github though.
They'll just use `git commit -n` to skip the pre-commit hook.
And if it's verified in CI (e.g. with Danger), someone will just create a script to automate writing commit messages that pass the good commit message test.
* Easier for your reviewers to understand the context of the change => easier to review the code (I think this alone makes it worth it)
* Keeps you accountable to yourself. By clearly describing the problem and the solution, it gives you an opportunity to spot when you're solving the wrong problem, not solving the whole problem, or solving it the wrong way, or including unrelated changes in your commit.
* Provides historical context. Sometimes over time the purpose of a particular part of the code becomes less clear, but with git blame you can immediately see it, assuming the commit message describes it well enough. (Yes, ideally the code will speak for itself or be supported by comments, but we don't live in an ideal world - and the comments may not be in the part of the code you are reading)
* Teaching opportunity - e.g. "we use approach A because the more obvious approach B has problem X" (again, sometimes better captured in comments, but not always)
Also your text editor may be able to help with things like 50 and 72. Vim’s gitcommit syntax and ftplugin files, for example, highlight the first 50 characters of the first line differently and set textwidth to 72, so that if 'formatoptions' includes t it’ll auto-wrap as you type, and you can use the likes of gqap to rewrap a paragraph. (If you change commentchar, you’ll want to copy and change those files to match. Hmm, should contribute a patch to make it configurable with a variable. Probably won’t get to it, though.)
https://git-scm.com/docs/git-commit#Documentation/git-commit...
This way you can use basically anything at the start of the line (as long as the whole line isn't the scissors)
You’re quite right, scissors is better. TIL! I have now set my commit.cleanup to scissors. I could even drop core.commentchar and my gitcommit and gitrebase syntax and after/ftplugin/ patches.
Generally I think it is good form to break down your stories so they match the scope of a reasonable merge request and name the branch [issue-id]-human_readable_text
I think this is pretty harmful to having a useful git history and a conspiracy theorist would likely say that it is intentional lock-in.
I.e. something like _"problem: suboptimal routing for upstreams in different datacenters; solution: additional role to separate primary and secondary upstreams"_.
Also I consider commit message as a message to someone who would deal with that piece of code maybe years from now. And he would wonder why these lines even exist, and if he can replace them with some new logic.
It doesn't enforce Conventional Commits, but I use this[1] to lint my commits in conjunction with a Sublime Linter plugin[2].
[1]: https://yossarian.net/snippets#git-lint-commit
[2]: https://packagecontrol.io/packages/SublimeLinter-contrib-git...
In Emacs, magit and git-commit-mode accommodate this well, I just found. Setting git-commit-summary-max-length to 50 and fill-column to 72 has the behavior that a visual warning will be displayed if it goes over 50 chars but it'll only wrap at 72. The body also wraps at 72, of course.
I understand that for projects with hundreds of collaborators the needs are different. But in our project with a team of 8 people I feel the overhead of structured commit messages is not worth it.
Commit messages are part of the repository itself, meaning they're replicated (backed up) locally every time you pull. They can be viewed using any tool that operates on Git repositories, e.g. https://magit.vc.
Review comments are stored out of band though. Curiously in another hidden git branch of the repo.
Refactor widget (#1234)
* wip
* wip
* wip
* fixes
* pr review
I opened the old PR on Github, and all it contained was a link to the Trello card for the task, where I finally found some context and description for the changes.It's ridiculous.
# See: https://example.com/
# See: Issue #123 <https://example.com/issue/123>
I have been putting the issue number at the start of the title so I could see which commits were related, but it seems like people would not want to do that with a 50-char subject line.
# Co-authored-by: Firstname Lastname <f.lastname@company.com>
so I can quickly uncomment this and fill in the name of the person that helped to author the change. It even is recognized by gitlab and github./*
/* Procedure Name:
/*
/* Original procedure name:
/*
/* Author:
/*
/* Date of creation:
/*
/* Dates of modification:
/*
/* Modification authors:
/*
/* Original file name:
/*
/* Purpose:
/*
/* Intent:
/*
/* Designation:
/*
/* Classes used:
/*
/* Constants:
/*
/* Local variables:
/*
/* Parameters:
/*
/* Date of creation:
/*
/* Purpose:
* /