Commit Messages That Actually Help
AI generated
git
HEAD
Git · Commit Messages · Version Control · DevOps
Commit Messages That Actually Help
Clear history instead of cryptic one-word commits

A good commit message explains why a change was necessary, not just what changed in the code, since the diff already shows that. With the 50/72 rule, an imperative subject line, clean trailers for ticket references, and optionally Conventional Commits, the Git history becomes a searchable archive that significantly speeds up debugging and code archaeology months later.

11 min read 50/72 rule Conventional Commits Git trailers

1. Why the commit message matters more than the diff itself

The diff shows line by line what changed, but it never answers why the change was necessary. A commit without a meaningful message forces every later reader to reconstruct the motivation from context: from linked tickets, from Slack threads, or in the worst case from pure guesswork. In a Magento project with hundreds of modules and multiple developers, that is not a theoretical problem, it is daily practice with every git blame on a critical line of code.

A good commit message is documentation that never goes stale, because it is written exactly at the moment the context is still fresh in mind. Writing it costs two minutes, but it saves a multiple of that during every later debugging session, code review, or bugfix backport. Treating commit messages as a tedious formality just shifts the cost onto later colleagues, often onto yourself six months from now, once the context is long gone.

2. The 50/72 rule: formatting the subject line and body correctly

The 50/72 rule is the longest-established formatting convention for commit messages, originally coming from Git's own documentation recommendations. The subject line stays at 50 characters or fewer, and the body wraps at 72 characters per line. The reason is purely practical: git log --oneline truncates long subject lines, and git log in its default format indents the text by four spaces, so a 72-character body stays readable in a classic 80-column terminal without additional wrapping.

There is always a blank line between the subject and the body, otherwise Git interprets the entire message as a single long line in tools like git shortlog or in GitHub commit lists. Many editors and IDEs, including PhpStorm, visually mark the 50 and 72 character limits directly in the commit dialog. Ignoring these limits consistently produces a history that becomes equally unreadable in git log --oneline, in pull request lists, and in terminal output.

3. Imperative mood and a precise, specific subject line

The subject line follows the imperative mood, not the past tense. "Fix null pointer in checkout observer" instead of "Fixed null pointer" or "Fixing null pointer". The test for this is simple and works in any language: the line must fit grammatically into the sentence "If applied, this commit will ...". Git itself follows this convention for automatically generated commits like merges and reverts, which is another reason to keep your own style consistent with the rest of the tooling.

A good subject line is also specific, not generic. "Fix bug" or "Update code" carry no information that isn't already visible in the diff. "Fix null pointer in checkout observer when quote has no items" immediately narrows down which module and which scenario are affected, without anyone having to read the body at all. Important: no trailing period on the subject line, a convention dating back to the early Linux kernel commit guidelines that has since become standard across practically every open-source project.

4. Explaining the why, not the what: the diff already shows it

The most common mistake in commit bodies is repeating what the diff already shows. "Changed the observer class to check for null" only describes the code, not the reasoning behind it. The body should instead explain why the change was necessary: which bug was reproduced, which alternative was rejected, and which side effect reviewers should watch for.

For a Magento observer fix, that means concretely: not just "added null check", but the context that the observer crashes on guest orders without a set customer group, because a third-party module fires the event before the standard validation runs. That information appears nowhere in the diff, yet it is essential for anyone who later touches that spot, so they don't fall into the same trap again or accidentally revert the fix.

5. Referencing ticket IDs and using trailers (Refs, Fixes, Co-authored-by)

Trailers are structured metadata lines at the end of the commit body in the format Key: Value, which Git and tools like GitHub or Jira can parse programmatically. Refs: JIRA-1234 links a commit to a ticket without automatically closing it. Fixes: #456 automatically closes a GitHub issue once merged into the default branch. Co-authored-by: Name <email> visibly credits pair-programming partners in the history and in GitHub's contribution statistics.

It's important to keep a clear separation between the free-text body and the trailer block: trailers always form the last paragraph, separated from the body by a blank line, one line per trailer. git interpret-trailers can parse and validate these lines automatically, which can be used in CI pipelines to consistently reject commits without a ticket reference, if the team has made that mandatory.


$ git commit

Fix null pointer in checkout observer

The Sales_Order_Place_After observer accessed the customer
group attribute directly. Guest orders created via the B2B
quote API do not set this attribute before the event fires,
which caused a fatal error during checkout.

Add a null check and fall back to the default customer group
id instead of failing the whole order placement.

Refs: JIRA-2481
Fixes: #742
Co-authored-by: Anna Schmidt <anna.schmidt@mironsoft.de>

6. Worked example: turning a bad commit message into a good one

The difference becomes most concrete with a real case: a Magento observer for sales_order_place_after throws an exception because it accesses an attribute that isn't set yet on quotes coming from the B2B module. The bad version settles for a single word, while the good version explains the cause, the fix, and the context in a few lines, exactly following the 50/72 rule and with a trailer for the ticket reference.

The difference doesn't show up while writing, it shows up months later during a git blame on exactly that line. The good version immediately answers why the check exists, so nobody accidentally removes it as redundant. The bad version forces a fresh investigation, in the worst case reproducing a bug a colleague already fixed months earlier.


# BAD: no context, cannot be searched, hides the actual problem
git commit -m "fix bug"

# BAD: describes the diff, not the reasoning behind it
git commit -m "added null check in observer"

# GOOD: imperative subject, 50/72 rule, explains the why
git commit -m "Fix null pointer in checkout observer" -m "
The Sales_Order_Place_After observer read the customer group
attribute directly. Guest orders from the B2B quote API do
not set this attribute before the event fires, causing a
fatal error during checkout for a subset of B2B customers.

Fall back to the default customer group id instead of
assuming the attribute is always present.

Refs: JIRA-2481"

7. Commit templates with git config commit.template

A commit template is a local text file that Git pre-fills in the editor every time you run git commit without a -m flag. Running git config commit.template ~/.gitmessage activates the file globally for all repositories. The template can contain comment lines reminding you of the 50/72 rule, placeholders for trailers, and even team-wide mandatory fields like a ticket reference, visible before the commit instead of being added tediously after the fact.

In a team setting, the template can be versioned and set per repository via git config --local commit.template .gitmessage, so every developer gets the same structure right after cloning, as long as a setup script sets the config value automatically for everyone involved. Comment lines starting with # are automatically stripped by Git from the final commit and never end up in the history.


# Store the template in the repo and share it with the team
cat > .gitmessage <<'EOF'
# Subject: max 50 chars, imperative mood, no trailing period
#
# Body: wrap at 72 chars. Explain WHY, not WHAT (the diff
# already shows what changed). Mention alternatives you
# rejected and side effects reviewers should watch for.
#
# Refs: JIRA-XXXX
# Fixes: #XXXX
EOF

git config --local commit.template .gitmessage

8. Conventional Commits as an optional convention

Conventional Commits structure the subject line following the pattern type(scope): description, for example fix(checkout): handle missing customer group in guest orders. The types feat, fix, chore, refactor, docs, and test are machine-readable and enable automatic changelog generation as well as automatic semantic versioning through tools like semantic-release or commitlint.

For smaller teams or internal Magento projects without a public package release, Conventional Commits is an option, not a requirement: the 50/72 rule and an explanatory body remain more important than the prefix in every case. Teams that choose the format should enforce it consistently via a commitlint hook in the CI pipeline, otherwise the convention drifts apart within a few weeks as individual commits simply ignore the schema.


# Conventional Commits: type(scope): description
git commit -m "fix(checkout): handle missing customer group in guest orders"
git commit -m "feat(catalog): add bulk price import via CSV"
git commit -m "refactor(observer): extract validation into a service"
git commit -m "chore(deps): bump hyva/theme-fallback to 1.3.2"

# BREAKING CHANGE footer triggers a major version bump
git commit -m "feat(api): remove deprecated v1 product endpoint" -m "
BREAKING CHANGE: v1 endpoints are removed. Clients must
migrate to /rest/V2/products before the next release."

9. Reading history productively: git log, git blame, and comparison

A consistent commit history pays off most clearly with git log and git blame. Running git log --oneline --graph --decorate lets you scan the evolution of a feature in seconds, provided the subject lines are meaningful and not all just "wip" or "fix". git log --format with a custom pretty-format shows exactly the fields that matter for code archaeology: author, date, subject line, and any trailer content.

git blame -L 40,60 file.php shows the commit behind every single line, and only a good message actually makes that information useful. With poorly documented history, git blame is usually just the first step, followed by git show <hash> to read the full diff, and in the worst case, asking the original author, provided they're still on the team. The table below compares bad and good practice at a glance.


# Compact, decorated overview of recent history
git log --oneline --graph --decorate -20

# Custom pretty-format for code archaeology
git log --format="%h %ad | %s [%an]" --date=short -- app/code/Mironsoft/SeoSuite

# Show trailers explicitly for a given commit
git show --format="%B" -s a1b2c3d | git interpret-trailers --parse

# Find who touched a specific line and why
git blame -L 40,60 app/code/Mironsoft/SeoSuite/Observer/SalesOrderPlaceAfter.php
Aspect Bad practice Good practice Benefit
Subject line fix stuff Fix null pointer in checkout observer Names the module and the bug concretely
Formatting Everything on one line, over 100 characters Subject <= 50 chars, body wrapped at 72 Readable in git log --oneline and terminals
Body content Repeats the diff ("added null check") Explains the cause and context of the bug Prevents re-investigation during git blame
Ticket reference Missing entirely or only in a Slack chat Trailer Refs: JIRA-1234 in the commit Traceable even after Slack history is gone
Scope of change One commit for five unrelated fixes One commit per logical change Enables targeted revert and cherry-pick

In practice, all five aspects are connected: an unclear subject line almost always comes with a body lacking context, and together they make later cherry-picks and reverts needlessly risky. Consistently applying the recommendations from the table produces a history that reads like searchable documentation instead of a mere byproduct of version control.

Mironsoft

Git workflows, code reviews, and developer tooling for Magento teams

Ready to establish a clean commit history across your team?

We set up commit conventions, templates, and review processes for your Magento team, including CI checks for subject line length, mandatory trailer fields, and automated changelog generation.

Commit conventions

Documenting the 50/72 rule, imperative mood, and trailer standards for the whole team

Git hooks & CI

Integrating commitlint, templates, and pre-commit checks into your pipeline

Code review processes

Building pull request workflows with clear standards for Magento and Hyvä projects

10. Summary

The key rules for commit messages that actually help all address the same underlying problem: a history without context becomes a black box as soon as the original author has forgotten the details. The 50/72 rule keeps the subject line and body readable in git log --oneline and in the terminal. The imperative style keeps consistency with Git's own automatically generated commits. The body explains the why, not the what, since the diff already shows the what in full. Trailers like Refs, Fixes, and Co-authored-by make ticket references and contributors machine-readable.

The biggest lever lies in applying these principles consistently across the whole team, not in isolated cases. A commit template set up with git config commit.template noticeably lowers the barrier to entry, because the structure is already provided. Conventional Commits is an optional, additional schema for automated changelogs on top of that, not a substitute for an explanatory body. Teams that follow these principles consistently end up with a Git history that genuinely helps during every git blame and every round of code archaeology.

Commit Messages That Actually Help, The Essentials at a Glance

50/72 rule

Subject line max 50 chars, body wrapped at 72 chars, a blank line in between. Keeps history readable in git log.

Imperative and why

Subject line in the imperative mood, body explains the motivation, not content already visible in the diff.

Trailers & ticket references

Refs, Fixes, and Co-authored-by as structured, machine-readable metadata at the end of the body.

Templates & conventions

git config commit.template for consistent structure, Conventional Commits optional for automated changelogs.

11. FAQ: Commit Messages That Actually Help

1Why does the commit message matter more than the diff?
The diff only shows what changed, never why. The commit message documents the motivation while the context is still fresh.
2What exactly does the 50/72 rule mean?
Subject line at most 50 characters, body wrapped at 72 characters per line. Keeps git log --oneline and the indented default log readable.
3Why should the subject line use the imperative mood?
Fits grammatically into "If applied, this commit will ..." and matches the style of Git's own automatic commits like merges.
4What belongs in the body of a commit message?
The reason for the change, rejected alternatives, possible side effects. What changed is already visible in the diff.
5What is a Git trailer?
A structured Key: Value line at the end of the body, for example Refs: JIRA-1234, parseable programmatically via git interpret-trailers.
6How do I set up a commit template?
git config commit.template ~/.gitmessage globally, or --local commit.template .gitmessage per repository. Opens the file as a template in the editor.
7What are Conventional Commits?
Format type(scope): description for machine-readable subject lines, enabling automatic changelogs and semantic versioning. Especially useful for package releases.
8How do I show custom fields with git log --format?
With a pretty-format string like git log --format="%h %ad | %s [%an]" --date=short for hash, date, subject line, and author.
9How do I automatically close a GitHub issue?
With the trailer Fixes: #456 or Closes: #456. Once merged into the default branch, GitHub automatically closes the referenced issue.
10Difference between Refs and Fixes?
Refs links without closing, suited for partial steps. Fixes closes the issue automatically on merge, suited for complete solutions.