Skip to main content
Writing Useful Commit Messages:

Git commit message best practices

Summary

This article explains how to write Git commit messages that document intent, support collaboration, and scale across teams. It covers structure, tone, conventions, and common pitfalls.

Introduction #

Git is a distributed version control system that records changes as a sequence of commits. Each commit contains a snapshot of the code and a message that describes the change. That message becomes part of the permanent project history.

Poor commit messages force readers to inspect diffs to understand intent.

Clear commit messages allow you and others to review history, debug regressions, and reason about decisions without opening files. This article describes practices that lead to consistent, readable, and useful Git commit messages.

What a commit message is used for #

A commit message is not a summary of files changed. It is a record of why a change exists. Tools such as git log, git blame, and code review systems rely on commit messages to provide context.

Clear commit messages reduce friction in code reviews. Reviewers can understand intent before reading code. Future maintainers can trace decisions without contacting the original author.

Assume that you will read your own commit messages months later without context. Write for that reader.

Commit messages are also consumed by automation. Release notes, changelogs, and semantic versioning systems often parse commit messages to classify changes. A clear structure reduces manual work and errors.

A commit message consists of three parts: a subject line, an optional body, and optional footers. Each part serves a different purpose.

subject line

body

footer

The subject line is mandatory. The body and footer are optional but strongly recommended for non-trivial changes.

Writing a clear subject line #

The subject line is a short summary of the change. Many tools truncate or display only this line.

Follow these rules:

  1. Limit the subject line to 50 characters.
  2. Use the imperative mood, for example Add, Fix, Remove.
  3. Capitalize the first word.
  4. Do not end the line with a period.
  5. Describe what the commit does, not how it does it.

Example:

Add input validation for user registration

This phrasing matches how Git applies commits. Git conceptually reads it as “If applied, this commit will add input validation”.

Writing a useful commit body #

The commit body explains the reason for the change and any relevant context. It answers questions that the diff alone cannot answer.

Use the body to describe:

  1. Why the change was made.
  2. What problem it solves.
  3. Why a particular approach was chosen.
  4. Side effects or limitations.

Wrap body lines at 72 characters. This keeps text readable in terminals and email clients.

Example:

Fix race condition in cache initialization

The cache was initialized lazily without synchronization.
Under concurrent load this caused duplicate initialization
and inconsistent state.

The fix introduces a mutex to guard initialization and
ensures the cache is created only once.

The commit body explains the intent and context of a change. It captures information that cannot be reliably inferred from the diff.

Use the body to document the reason behind a decision, not to restate the code. Here are some examples or good commit bodies:

Explaining the problem being solved #

When a change fixes a defect or limitation, describe the observed behavior and its impact.

Example:

Fix incorrect tax calculation for negative values

The tax calculation assumed all inputs were positive.
When refunds were processed, this resulted in incorrect
rounding and negative totals.

Describing constraints or requirements #

If the implementation is shaped by external constraints, document them.

Example:

Add polling fallback for legacy browsers

The WebSocket API is not available in the supported
browser versions used by embedded clients. Polling is
used as a fallback to maintain compatibility.

Justifying design or implementation choices #

When multiple approaches exist, explain why one was chosen.

Example:

Replace recursive traversal with iterative approach

The recursive implementation exceeded the call stack
limit for large inputs. An explicit stack avoids this
limit and improves predictability.

Documenting side effects or risks #

If a change has consequences that are not obvious, record them.

Example:

Enable caching for user profile responses

Responses are cached for 60 seconds. Profile updates may
not be visible immediately during this window.

Providing migration or upgrade notes #

Changes that affect existing behavior should explain what downstream users must do.

Example:

Rename configuration key for timeout setting

The key <code>requestTimeout</code> replaces <code>timeout</code>.
Existing configurations must be updated to avoid fallback
to the default value.

Referencing external context #

Links to issues, specifications, or discussions belong in the body when they add context.

Example:

Align password rules with security guidelines

The updated rules follow internal security guidance
defined in SEC-42 and address recent audit findings.

Using footers for metadata #

Footers are used for structured metadata. They are separated from the body by a blank line and follow a Key: value format.

Common footer uses include:

  1. Referencing issue trackers.
  2. Marking breaking changes.
  3. Adding sign-off information.

Example:

Refactor authentication middleware

This change simplifies the middleware chain and removes
unused code paths.

Refs: ISSUE-123

Some workflows require a Signed-off-by footer to comply with contribution policies.

Atomic commits and message quality #

An atomic commit represents a single logical change. Atomic commits are easier to review, revert, and understand.

Commit messages benefit directly from atomic commits:

  1. The subject stays focused.
  2. The body remains concise.
  3. Reverts have a clear scope.

If a commit message requires many unrelated explanations, the commit likely contains too many changes.

Conventional commits and structured formats #

Conventional Commits is a specification for structured commit messages. It defines a format that includes a type, an optional scope, and a subject.

Example:

feat(api): add pagination to user endpoint

In this format:

  1. feat indicates a new feature.
  2. api is the optional scope.
  3. The subject follows the same rules as any subject line.

This structure supports automated tooling, including changelog generation and semantic versioning. Use it only if your project or organization has adopted it consistently.

Common mistakes to avoid #

Several patterns reduce the value of commit messages:

  1. Vague subjects like Update files or Fix bug.
  2. Describing the diff instead of intent.
  3. Combining unrelated changes in one commit.
  4. Relying on issue titles without explanation.
  5. Writing messages only for the present and ignoring future readers.

Each of these forces readers to spend extra time reconstructing context.

Amending and fixing commit messages #

Git allows you to edit commit messages after creation. Use git commit --amend to fix the most recent commit message before pushing.

For published history, changing commit messages rewrites history. This can disrupt collaborators. Only rewrite public history if your team explicitly allows it.

Positive commit message examples #

The following examples are aligned with GitHub Issues. GitHub recognizes footer keywords such as Closes #123 to automatically close issues when the commit is merged into the default branch.

Rate limiting for authentication #

This commit message works because the subject states the behavior change, the body explains the security rationale, and the footer provides clear traceability to the issue.

Add rate limiting to login endpoint

Prevents brute force attacks by limiting failed login
attempts to five per minute per IP address.

Closes #128

Bug fix with clear root cause #

This commit message works because the body explains the faulty assumption that caused the bug, making future regressions easier to reason about.

Fix null pointer exception in order export

The export job assumed a billing address was always
present. Guest checkouts violated this assumption.

Closes #742

Refactoring with technical justification #

This commit message works because it explains why the refactor was necessary and what problem the previous implementation caused.

Refactor date parsing to use ISO 8601 format

The previous custom parser failed for timezone offsets.
Using the standard format reduces ambiguity.

Closes #301

Cleanup of obsolete code #

This commit message works because it documents historical context that is no longer visible in the codebase, preventing confusion about the removal.

Remove deprecated feature flag handling

The flags were disabled in version 2.1 and are no longer
referenced by any active code paths.

Closes #19

Dependency update with security context #

This commit message works because it explains the risk addressed by the update instead of only stating that dependencies changed.

Update dependencies to address security advisory

Upgrades the HTTP client library to a version that fixes
a request smuggling vulnerability.

Closes #88

How to write multi-line git commit messages on the command-line #

There are several ways to write multi-line git commit messages on the command-line:

1. Using git commit without -m #

Simply run git commit without the -m flag, and Git will open your default text editor:

git commit

Git will open your configured editor (usually vim, nano, or VSCode) where you can write a multi-line message.

2. Using a multi-line string with -m #

You can use multiple -m flags for different paragraphs:

git commit -m "Title line" -m "Second line" -m "Third line"

Each -m creates a new paragraph in the commit message.

3. Using shell features for multi-line strings #

Bash/Zsh Using quotes and literal newlines (Bash/Zsh) #

git commit -m "Title line

Second line
Third line"

Press Enter after "Title line to continue on new lines.

Using a heredoc (Bash/Zsh) #

git commit -F- <<EOF
Title line

Second line
Third line
EOF

Using echo with newlines #

echo -e "Title line\n\nSecond line\nThird line" | git commit -F-

4. Set default editor #

If you prefer a specific editor for commit messages:

# Set nano as default
git config --global core.editor "nano"

# Set VSCode as default
git config --global core.editor "code --wait"

# Set vim as default
git config --global core.editor "vim"

5. Create a commit message template #

Set up a template for consistent commit messages:

# Create a template file
echo "Subject line (50 chars or less)
- Short summary of the change
- Begin with Add/Fix/Remove/...

Body (72 chars per line)
- Summary of changes
- Bullet points for details

Footer
- metadata" > ~/.gitmessage

# Configure Git to use it
git config --global commit.template ~/.gitmessage

FAQ's #

Most common questions and brief, easy-to-understand answers on the topic:

Why do Git commit messages matter?

They provide context for changes, support git log and git blame, and help teams understand why a change was made.

What tense should a commit message use?

Use the imperative mood, for example Add validation, which matches how Git applies commits.

How long should a commit subject be?

Keep the subject at or below 50 characters to ensure readability in tools and terminals.

Should every commit include a body?

No, but include a body when the change needs explanation beyond the subject line.

Can commit bodies include technical details?

Yes, commit bodies are the correct place to explain algorithms, constraints, trade-offs, or non-obvious implementation details.

Is it acceptable to reference tickets or pull requests in the body?

Yes, references provide traceability, but they should not replace a written explanation of the change.

Further readings #

Sources and recommended, further resources on the topic:

Author

Jonas Jared Jacek • J15k

Jonas Jared Jacek (J15k)

Jonas works as project manager, web designer, and web developer since 2001. On top of that, he is a Linux system administrator with a broad interest in things related to programming, architecture, and design. See: https://www.j15k.com/

License

Git commit message best practices by Jonas Jared Jacek is licensed under CC BY-SA 4.0.

This license requires that reusers give credit to the creator. It allows reusers to distribute, remix, adapt, and build upon the material in any medium or format, for noncommercial purposes only. To give credit, provide a link back to the original source, the author, and the license e.g. like this:

<p xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/"><a property="dct:title" rel="cc:attributionURL" href="https://www.ditig.com/git-commit-message-best-practices">Git commit message best practices</a> by <a rel="cc:attributionURL dct:creator" property="cc:attributionName" href="https://www.j15k.com/">Jonas Jared Jacek</a> is licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank" rel="license noopener noreferrer">CC BY-SA 4.0</a>.</p>

For more information see the Ditig legal page.

All Topics

Random Quote

“PowerPoint corrupts absolutely.”

Vinton Gray Cerf American Internet pioneer, one of "the fathers of the Internet"geoff-hart.com, - IT quotes