Programming Beginner 6 min

How to Write Good Git Commit Messages

A commit message is a note to your future self (and your teammates) explaining why a change was made. The diff shows the what; the message should explain the why. Good messages pay compound interest — every time someone runs git log or git blame to understand a decision, they either thank you or curse you.

Step-by-step

  1. 1

    Keep the subject line under 50 characters

    The first line of a commit message is the subject. GitHub truncates it at 72 characters; many terminals at 80. Aim for 50 so it fits cleanly in git log --oneline, GitHub's list view, and email notifications. If you can't summarise in 50 chars, the commit is probably too large.

    bash
    # Too long — hard to scan
    Fix the issue where authenticated users were being redirected to the login page after session expiry instead of refreshing the token
    
    # Good
    Fix token refresh redirect for authenticated users
  2. 2

    Use the imperative mood in the subject

    Write the subject as if completing the sentence \"If applied, this commit will…\". That means: Fix, Add, Remove, Update, Refactor — not "Fixed", "Adding", or "Various changes".

    bash
    # Wrong
    Fixed broken login
    Adding dark mode support
    I changed the timeout value
    
    # Correct
    Fix broken login redirect
    Add dark mode support
    Change timeout from 3s to 5s
  3. 3

    Leave a blank line before the body

    Git treats the first paragraph as the subject and everything after a blank line as the body. Without that blank line, tools like git log --oneline, GitHub, and many email clients cannot distinguish subject from body. The blank line is not optional.

    bash
    Fix token refresh redirect for authenticated users
    
    The session middleware was checking token expiry before the refresh
    handler ran, causing a redirect to /login even when a valid refresh
    token existed. Reorder the middleware chain to fix this.
    
    Fixes #218
  4. 4

    Explain why, not what, in the body

    The diff already shows what changed. Use the body to explain the reason, the trade-off chosen, or the alternative you rejected. Wrap lines at 72 characters so the body renders cleanly in terminals.

    bash
    # Unhelpful body — just describes the diff
    Changed timeout from 3000 to 5000 in config/app.js
    
    # Helpful body — explains the reasoning
    Increase API timeout from 3s to 5s
    
    The third-party payment gateway occasionally takes 4–4.5s on the first
    request after a cold start. Users were seeing spurious payment errors.
    The gateway's SLA guarantees responses within 5s.
  5. 5

    Adopt Conventional Commits for automation

    Conventional Commits is a lightweight standard that prefixes the subject with a type: feat:, fix:, chore:, docs:, refactor:, test:. Tools like semantic-release and standard-version read these prefixes to auto-generate changelogs and version bumps — no manual release notes.

    bash
    feat: add OAuth2 login with Google
    fix: correct cart total when discount code applied
    chore: upgrade Laravel from 11 to 12
    docs: document the deployment workflow in CLAUDE.md
    refactor: extract payment logic into PaymentService
    test: add unit tests for CartController
    
    # Breaking change — add ! and a footer:
    feat!: rename /api/v1 to /api/v2
    
    BREAKING CHANGE: All clients must update their base URL.
  6. 6

    Reference issues and pull requests

    Link commits to the issue tracker so future readers can jump straight to the full discussion. GitHub closes issues automatically when a commit with Fixes #N lands on the default branch.

    bash
    fix: prevent double-submit on payment form
    
    Disable the submit button immediately on click and re-enable it only
    if the API returns an error. Previously, slow connections allowed
    users to click twice, creating duplicate charges.
    
    Fixes #312
    See also: #298
  7. 7

    Configure your editor for commit messages

    Writing multi-line commit messages in the terminal is painful. Set your preferred editor so Git opens a real file when you run git commit (without -m). VS Code's --wait flag tells it to block until the file is closed.

    bash
    # VS Code
    git config --global core.editor "code --wait"
    
    # Vim
    git config --global core.editor "vim"
    
    # Nano
    git config --global core.editor "nano"
    
    # Then just run:
    git commit
    # Git opens the editor; save & close to finalise.

Tips & gotchas

  • "wip", "minor fix", "updates", and "asdf" are not commit messages. Your future self at 2 AM debugging production will not thank you.
  • If you need the word "and" to describe a commit, it should probably be two commits.
  • The <code>git log --oneline --graph</code> view rewards good subject lines — bad ones make the log unreadable.
  • For teams, add a <code>.gitmessage</code> template file and set <code>git config commit.template .gitmessage</code> to nudge everyone toward the standard.

Wrapping up

A good commit message takes 90 extra seconds to write and saves hours of investigation six months later. The habit compounds: once your whole team writes clear messages, git log becomes a searchable decision journal rather than a list of timestamps and SHA hashes. Start with imperative mood and a 50-char subject line — everything else follows naturally.

#Git #Workflow #Best Practices
Back to all guides

Need Help With Your Project?

Book a free 30-minute consultation to discuss your technical challenges and explore solutions together.