Hacker News new | past | comments | ask | show | jobs | submit login
Using Git commit message templates to write better commit messages (gist.github.com)
184 points by ingve on Jan 12, 2022 | hide | past | favorite | 79 comments



This is a good idea, but in practice after a few commits you learn all the principles and don't need a wall of text in your commit template. Getting feedback in your PRs about your commit messages helps quickly reinforce learning these best practices. (you do review the commit message when doing PRs, right? ;)

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.

[1] https://www.conventionalcommits.org/en/v1.0.0/


I've always seen conventional commits as an anti-pattern as it seems to discourage developers from writing small, atomic commits.

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.


Conventional Commits restricts each commit to a single type of change - no mixing bug fixes, features, and refactoring. This supports exactly what you want - small, atomic commits.

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.

[1] https://trunkbaseddevelopment.com/


I nearly never look at individual commits in a PR.

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.


> I've always seen conventional commits as an anti-pattern as it seems to discourage developers from writing small, atomic commits.

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’m a big fan of Conventional Commits specification too. I always been strict with myself when writing commit messages, but I have members of my team that were a mess. So, it’s good for establishing a clear pattern for everyone to follow. Also, by implementing it, you gain a lot of flexibility in terms of the interaction of your code being pushed and the CI/CD pipeline. (e.g auto release generation with semver)

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 also like the problem/solution format, it gives a ton of context.


Please - be aware of what you're doing relying on conventional commits. I saw projects using them to later do automatic merges between branches, and I think this is the "wrong level of abstraction". I think there is an underlying problem if you are mixing "fix" and "feature" (for example) commits in a "feature" branch. Why the fix is in a feature branch? I think it's more important to have the distintion on a "branch" level, linked in a 1-1 relation to a jira/feature/bugtracker issue and whatnot.


Now you're talking about branching strategies, not commits, which is a different and much larger topic :)

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/


I hadn't seen Conventional Commits; my in-head template looks very similar, with the addition of a "Situation" before the "Problem". On a big project, the reviewer may not know (or may not remember off-hand) exactly what the code does at the moment. So saying, "The code currently does X" is a helpful prompt, so that when they read "That's a problem because of Y", they have all the key context in their head (rather than perhaps having to guess and/or look at the code to figure out what's going on).


We have a:

What

Why

How

Related


isn't the problem described in the jira ticket?


> isn't the problem described in the jira ticket?

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.


Organizations ought to preserve tickets across migrations to new systems, but they don’t always. Only commits are forever.


Except tons of orgs squash before merge (unfortunately, IMHO) so individual commits are pretty transitory. Doing “conventional commits” for all those smaller commits seems wasteful.


They often believe they'll never change ticket systems. Until they find out one morning IT didn't subscribe to the same ideology.


Nice to have the problem in line because I can search git history. Also no guarantees that the Jira ticket will be around as long as the code.


The problem the commit is addressing might not be exactly the same as the problem in the JIRA ticket (e.g. you're solving a bigger problem than just what the JIRA describes, or there are multiple causes and this commit fixes only one of them, etc).


A useful, related trick. Enable commit.verbose to have Git append a diff to the commit message template, which you can quickly consult when writing a commit message.

  git config --global commit.verbose true
https://tekin.co.uk/2020/03/git-commit-verbose-mode


Thank you; here I'd been using an alias for `git commit -v` all this time:)


wow thanks for this! I often find myself amending after running `git show` to get this information


My commit messages usually just try to answer the prompt “why does this commit exist?” That’s because future me will be asking that when I open the commit after finding it in `git blame`.


The prompt that I use in my head is finishing the sentence "Applying the diff in this commit will...". Examples:

    Change the submit button color to green
    Bump library XYZ to 1.2.3
    Refactor and simplify the cart repository


The problem with that is you'll start reworking on a part of a project then you'll find something in the code and say, "Why was this done this way? It'd be so much easier just to do this instead."

Then 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.


> Why was this done this way? It'd be so much easier just to do this instead

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".


> there should be a comment in the code explaining the thought process

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.


In my experience, the code produced by these kinds of coworkers is neither good nor self-explanatory.


my commit message template: "[profanity], wow this has been sitting for [number of months] with uncommitted changes that i don't remember the purpose of, committing so that if i break more stuff i have a point to roll back to"


My messages tend to be:

“Progress”


A noble effort but in my experience these just get ignored. 90% of the PRs at my org have the empty, unfilled template text at the top of the description, a few line breaks, and then the actual description underneath.


That's unfortunate, and I think your experience reflects most places I've worked at too. It doesn't have to be that way though; technical leadership on a team can choose to care about this kind of thing and coach their colleagues into caring more about writing communicative commit messages.

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...


1. Implement automation to flag and block/reject bad commit messages.

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.


> Implement automation to flag and block/reject bad commit messages.

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.


And those are the people you fire because they refuse to follow policy and actively work to circumvent best practices and quality gates.


No way, (1) would annoy the heck out of people. Plus what if you want to just save off your current status and log off for the day..


Configure the enforcement only for `main` and other release/production branches, let people push whatever they want to their dev/feature branches.

When those branches are attempted to be merged they'll fail due to having bad commits and require fixing.


That's what git stash is for; you can even add a message, just like a commit, to the stash. A commit should always be in a good state, runnable, without lint or other errors.


Yes. In my experience, anything that prevents people from committing to local branches is incredibly annoying. You want people to commit frequently and “save their work.” Checks, quality barriers, etc. are fine at CI time.


> Plus what if you want to just save off your current status and log off for the day

Strictly speaking, that's what git stash [1] is for. Not so ideal if you want to push your WIP changes to Github though.

[1] https://git-scm.com/docs/git-stash


We immediately kick them back if this is the case.


I always take care to delete the template text first.


fwiw, the template text in this case are comments so that gets stripped


If I had a dollar for everytime one of my team mates wrote a commit message as "Fixed the bug" or "Update filename.js", I would be quite rich.


Someone in my organization (senior to me) often commits with the message "progress"


My approach is to reserve the verbose discussion for the issue tracker and then keep the commit message short with "fixes #NNN:" and a short description of what's changed without motivation, design considerations etc.


What advantage do long commit messages have? In most open source projects, commit messages are very short. however, it is extremely easy to follow the progress through history as the code changes are always kept less comprehensive. but I would like to listen to the advantages of writing long commits.


I can't speak about it in the context of open source projects, but here are some general benefits:

* 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)


A related thing of Git settings: if you ever want to start a line with #, change core.commentchar to something else. I use a semicolon. (`git config --global core.commentchar \;`)

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.)


I prefer scissors.

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)


The really funny thing here is that I’m used to seeing the scissors line because I set commit.verbose, but I’d been puzzling from time to time over what that >8 in the middle of the line was, and just a few hours ago I finally realised (prompted no doubt by dwelling on core.commentchar) that it was ASCII art scissors. >8 feels weird, even though it’s the direction a right-hander would cut paper; 8< feels more natural for some reason (maybe because LTR language?).

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.


As long as it says why not what (and in which case inherently doing a worse job of it than the code) it beats the vast majority IMO!


I like it, but I feel like it is closer to a merge request message / merge / rebase commit message.

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


Agreed. I can't imagine working on a feature branch and writing 50 of these for a moderately large change, just for those to be squashed. I mean, I can, but I don't think it's necessary.


It that case you can put in the detailed commit messages later. So when you are doing your squash you type in the detailed commit.


I wish that github would stick multiple commit messages into the default pr body - if I have a single commit it'll prefill the pr body from its message, but have a series of commits it won't. Should use the same rules as it's default squash message IMO


GutHub support for reviewing multiple commits is basic (to be generous). The focus is all on the "PR" the entire diff and the description (that lives outside of git).

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'd love to be able to browse pr's commit by commit marking files as viewed, and then ideally have this bubble through to the holistic change view


I'm a big fan of problem/solution format.

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.


I had no idea `git` supported a template for commit messages. Very cool!

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...


I hadn't hear of the 50-char recommendation before, just the 72-char limit.

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.


Hard-wrapping is a mistake. It seems like you can keep making the line-length shorter to make it like likely to run into the double-wrapping problem but the best solution is not to wrap and let the viewer wrap at their preferred width.


For me, commits are too low granularity. I usually give 1-2 sentence summary. I put detailed descriptions (sometimes multi-page with graphs and tables) into pull-requests.

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.


Pull requests are trapped inside Microsoft's proprietary platform (Github) and viewable only on the web for as long as Microsoft sees fit to give you access. Good luck getting that history out if your Github account is ever disabled for any reason.

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.


The other day I tried doing a git blame on a line of code. Since we use squash + merge for PRs, it pointed to a 1000 line commit with a message similar to:

  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.


Oh man. I had a coworker once who would frequently commit with a single word commit "fixup" - often as you'd expect at the end of a PR but never squashed or put anymore detail into what they actually did, often much more than simple whitespace or linting changes you'd expect from the message, so you had to be really careful to check. If you then searched the commit history for "fixup" 100s of commits would show up. The person in question had so many other issues working in this team that this particular one was never brought up but it drove me mad.


At least it told you Refactor widget. ;p Even worse is repos where the first line of every commit message is just “Merge pull request #12345 from user/fixstuff into master.”


At least with Gitlab, the text of the merge request is used for the text of the merge commit that’s created when you merge in the changes, so the comments still get added to the Git history.


It would be nice if pull requests and issues were integrated into git somehow, to avoid centralization like this.


Gerrit avoids this problem neatly by not having any PR message at all, the only place you can provide context is the commit message, which obviously gets tracked together with the repo.

Review comments are stored out of band though. Curiously in another hidden git branch of the repo.


I haven’t tried it, but it is my understanding that the source code version control system made by the SQLite team does this.


How large are your commits to need templates? Should a feature branch always be squashed into one commit before PR?


Good work! Here's our git commit template which we use with tags for categories, keywords for searches, links to issue trackers, etc.

https://github.com/joelparkerhenderson/git-commit-template


Does anyone else use these kind of trailers like you do?

# 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.


Yes optionally, because trailers with links can be especially helpful when a commit is relating to specific URLs, such a relevant blog post, or design image, or planning document, or industry publication, or committee RFC.


I like to include the following line in my template:

    # 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.



I use this one from "How To Write Unmaintainable Code".

/*

/* 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:

* /


I'm reading the comments here and thinking I'm some outdated greybeard. Do people actually spend more time now trying to write commit messages than doing actual work? I would never put any of my teams to do something like this, do actual developers LIKE spending more time to actually write these messages? Is code today so hard to understand and read (what about comments) that you need these things for reviews?


You should just fill out a TPS report instead.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: