Last updated:
How to Write Git Commit Messages | Character Limits and Best Practices
Git commit messages are essential clues for understanding a codebase's change history. Well-written messages dramatically improve the efficiency of code reviews and bug investigations months later. Conversely, vague messages become technical debt that drags down the entire team's productivity. This article covers character count guidelines and practical best practices for commit messages. Use Character Counter to verify your message length.
How Binding Are the Character Count Guidelines?
The "50-character subject, 72-character body wrap" convention traces back to the email era. Git creator Linus Torvalds carried over the practice of sending patches via email during Linux kernel development. Email clients typically displayed 80 columns, and accounting for quote markers and indentation, 72 characters was the optimal body width.
It helps to be clear about what these numbers are not. Git itself imposes no length limit on either the subject or the body. A 200-character subject line commits without a warning, and no Git command rejects it afterward. The 50/72 figures are operational guidelines that grew out of how commit history is read, not constraints enforced by the tool, which is why individual projects are free to adopt stricter or looser limits of their own.
The guideline also comes with a practical catch that is easy to overlook. When a project adopts Conventional Commits, the prefix is part of the subject line, so a marker such as feat: or fix(auth): spends part of the same character budget before the description even begins. The longer the type and scope, the less room is left for the actual summary. Teams that use detailed scopes therefore do better to treat 50 characters as a target for the whole line rather than for the description alone.
The Origin and Technical Rationale of the 50/72 Rule
The 50/72 rule is rooted in the 80-column terminal width. It is worth being precise about where that width actually applies. In the default git log output (the medium format), the commit hash occupies a line of its own and the message is indented by 4 spaces, so the subject never competes with the hash for horizontal space. The 72-column figure comes from git log --oneline instead: there the abbreviated hash (7 characters) plus a following space consume 8 of the 80 columns, leaving roughly 72 for the subject. The recommended 50-character limit serves as a "soft limit" that keeps a comfortable margin within that width.
The 72-character body wrap also has a clear rationale. Patches generated by git format-patch are sent as emails, and mailing list replies prepend > (2 characters) for quoting. With two levels of quoting (> > = 4 characters) plus 4 characters of indentation, 72 characters is the maximum that fits within an 80-column display. This calculation is the explanation most widely shared for the 72-character rule, although Git's own manual does not actually specify a wrap width for the body.
Basic Structure and Character Count Guidelines
A Git commit message follows a two-part structure: a subject line and a body, separated by a blank line. This structure aligns with how git log --oneline and GitHub's commit list display only the subject.
| Element | Character Guideline | Reason |
|---|---|---|
| Subject line | 50 characters or less | Git's official manual (the DISCUSSION section of git commit) recommends a summary line of 50 characters or less |
| Subject (practical upper bound) | 72 characters or less | The width at which git log --oneline still fits an 80-column terminal without wrapping |
| Body line width | Wrap at 72 characters | Fits standard terminal width (80 cols) with indent margin |
| Body total | No limit | Add detailed explanations as needed |
Hosting services such as GitHub and GitLab also truncate long subject lines in their commit lists, but the exact cutoff varies from screen to screen and is not published by either service. It can also change without notice, and it depends on the viewer's window width. Chasing a specific digit count for a specific screen is therefore effort that rarely pays off. Keeping subjects at 50 characters or less is the reliable approach precisely because it stays comfortably inside whatever the current cutoff happens to be, on any of these screens and in a terminal alike.
Multibyte Characters in Commit Messages
When writing commit messages in languages with multibyte characters (such as Japanese, Chinese, or Korean), the difference between character count and byte count becomes a significant concern. Git internally handles strings as UTF-8, so a single CJK character consumes 3 bytes. GitHub determines subject truncation based on display width (column count) rather than byte count, and each full-width character occupies 2 columns. This means a maximum of 25 full-width characters fit within a 50-column display width.
Displaying git log in a terminal also raises width calculation issues with multibyte characters. Most terminal emulators render full-width characters at 2-column width based on the East Asian Width property, but some environments (particularly older Windows Command Prompt) miscalculate widths, causing misaligned output. If columns don't line up in git log --oneline, check your terminal's character width settings.
For teams that use non-ASCII commit messages, a hybrid approach - English prefixes with native-language descriptions - works well in practice. For example, fix(auth): ログイン時のセッション復元を修正 preserves the ability to filter by type with git log --oneline --grep="^fix" while remaining readable for native speakers.
Conventional Commits and Prefix Usage
Conventional Commits is a specification that gives commit messages a consistent structure. Adding a type prefix to the beginning of each message makes the nature of changes immediately identifiable and enables automated CHANGELOG generation and semantic versioning.
The design philosophy behind this specification centers on automatic integration with Semantic Versioning (SemVer). A feat commit corresponds to a MINOR version bump, fix corresponds to a PATCH bump, and commits containing a BREAKING CHANGE footer signal a MAJOR version bump. This mapping allows tools like semantic-release and standard-version to automatically determine version numbers from commit history.
The basic format is <type>(<scope>): <description>. The scope is optional and indicates the module or component affected.
| Prefix | Purpose | Example |
|---|---|---|
feat | New feature | feat(auth): add OAuth2 login support |
fix | Bug fix | fix(api): resolve null pointer in user endpoint |
docs | Documentation change | docs: update API reference for v2 |
style | Code style (no behavior change) | style: fix indentation in config file |
refactor | Refactoring | refactor(db): simplify query builder logic |
test | Adding or fixing tests | test: add unit tests for payment module |
chore | Build or tooling changes | chore: upgrade webpack to v5 |
perf | Performance improvement | perf(search): add index for full-text query |
ci | CI/CD configuration | ci: add GitHub Actions workflow |
Why Imperative Mood Is Recommended
The recommendation to use imperative mood in English commit messages is rooted in Git's own design. The messages Git generates automatically, such as Merge branch 'feature' and Revert "Add login form", are themselves written in imperative mood. Matching this style in user-written messages creates a unified voice throughout git log output.
An imperative message reads as a description of what happens when the commit is applied. Add user authentication means "this commit adds user authentication," directly expressing the commit's effect. In contrast, Added user authentication (past tense) reads as a report of what was done - a record of work rather than a description of the commit's effect.
When in doubt, try inserting your message into the sentence "If applied, this commit will ___." If applied, this commit will add user authentication reads naturally, while If applied, this commit will added user authentication is grammatically broken.
Good vs. Bad Commit Messages
| Bad Example | Problem | Good Example |
|---|---|---|
fix bug | Which bug? | fix(cart): prevent duplicate items on rapid click |
update | What was updated? | docs: add setup instructions for local dev |
WIP | Work-in-progress left in history | feat(ui): add skeleton loader for product list |
asdfgh | Meaningless string | refactor: extract validation logic into helper |
Fixed the thing that was broken... | Verbose, exceeds 50 chars | fix(auth): restore session after token refresh |
- Use imperative mood: Write "Add" not "Added," "Fix" not "Fixed." This matches Git's own generated messages (e.g., "Merge branch...").
- No period at the end of the subject: Saves space and follows convention.
- Write "why" in the body: The subject says "what," the body explains "why" the change was necessary.
- One commit = one logical change: Don't bundle unrelated changes into a single commit.
Squash Merge and Revert Message Design
When using squash merge, multiple commits are consolidated into one, so message design differs from regular commits. GitHub's squash merge defaults to using the pull request title as the subject and listing individual commit messages as bullet points in the body, though this default can be changed in the repository settings. This auto-generated message tends to be verbose. For squash merges, it's more effective to use the pull request description as the body, concisely summarizing the purpose and scope of the change.
For revert commits, the convention is to use Git's auto-generated format: Revert "<original subject>". In the body, include the original commit hash (This reverts commit <hash>.) along with the reason for the revert. Without a reason, it becomes impossible to understand "why it was reverted" when tracing history later. When reverting multiple commits at once, explicitly state the range - such as revert: undo feature X (commits abc..def) - to make history tracking easier.
Team Commit Message Conventions
While personal projects allow flexibility, team development requires unified rules. The following practices help maintain message quality across an organization:
- commitlint: Automatically checks messages against Conventional Commits rules. Integrate into CI to catch violations before merge.
- Git hooks (husky): Use
commit-msghooks to validate message format locally. - Templates: Set a shared template with
git config commit.templateto prevent omissions. - Review messages in PRs: Include commit message quality as part of pull request reviews.
Here's a practical setup combining commitlint and husky. Install with npm install --save-dev @commitlint/cli @commitlint/config-conventional husky, then create commitlint.config.js at the project root with module.exports = { extends: ['@commitlint/config-conventional'] };. Initialize husky with npx husky init, and add npx --no -- commitlint --edit $1 to .husky/commit-msg. This automatically validates every commit message on commit.
Overly strict rules can slow development, so adopt them incrementally. Start with prefix standardization, then introduce commitlint once the team is comfortable.
AI-Generated Commit Messages: Benefits and Caveats
GitHub Copilot and various editor extensions offer automatic commit message generation that significantly reduces the effort of writing messages. These tools analyze the diff to summarize what changed, and their accuracy for describing "what was changed" tends to be high.
However, auto-generation has clear limitations. The biggest weakness is the inability to infer "why the change was necessary." Code diffs don't reveal the motivation or business context behind a change, so the "Why" that belongs in the body must be supplied by a human. Additionally, auto-generated messages tend to be verbose and frequently exceed the 50-character subject limit. Rather than using generated output as-is, always review it, trim unnecessary details, and condense it into a concise message.
Conclusion
The widely accepted convention for Git commit messages is a subject line of 50 characters or less with body text wrapped at 72 characters. These numbers derive from the 80-column terminal width and email quoting conventions, and they remain operational guidelines rather than rules: Git itself places no limit on message length. Conventional Commits prefixes make change types instantly recognizable and support automated CHANGELOG generation. When writing messages with multibyte characters, be mindful of display width - full-width characters occupy 2 columns each, so aim for about 25 characters for the subject. Good messages concisely convey "what" and "why," while bad messages are vague and lack information. Use Character Counter to check your commit message length.