Abdurezak
Published on

Git Commit Messages Best Practices

Abdurezak Farah

Abdurezak Farah

@abdurezakfarah

Introduction

When it comes to software development, clear communication is everything – and that includes the way you write Git commit messages. It might not seem like a big deal at first, but good commit messages can really make a difference when it comes to understanding and maintaining a project. In this guide, we're going to break down some of the best practices for writing Git commit messages, so your commit history stays clean, easy to read, and super helpful for everyone working on the project. Let’s dive in!

Why Git Commit Messages Matter

Writing great Git commit messages might seem small, but it’s actually a huge part of making your project easy to understand, collaborate on, and maintain. Let’s break down why clear commit messages are so important.

Clarity and Readability

Good commit messages make it easy for developers to understand what’s changed without needing to dig through the code. This is super helpful in team environments, where everyone needs to be on the same page.

Understanding Changes Quickly

In a big codebase, figuring out what changed and why can be tough. Clear, descriptive messages like "fix: correct calculation error in billing module" tell the whole story at a glance. Instead of hunting through files, you’ll know right away what was fixed, added, or updated – saving a ton of time.

Enhancing Onboarding

For new team members, joining a project can be overwhelming. But clear commit messages act like a timeline that shows how the code has evolved. By reading through them, newcomers can quickly get a feel for what’s been done, why certain decisions were made, and how things work – making onboarding way smoother.

Simplifying Code Reviews

Code reviews are easier when commit messages explain the intent behind changes. For example, "refactor: simplify database query logic" tells the reviewer that the goal was to clean up the code without changing its behavior. This makes the review process faster and more focused since the reviewer knows exactly what to expect.

Facilitating Collaboration

When you’re working with a team, staying in the loop is key. Clear commit messages like "add: implement user profile page" keep everyone updated on what’s happening. It helps avoid conflicts, sync up efforts, and keeps the whole team moving in the same direction.

Anatomy of a Good Commit Message

Let’s dive into what makes up a solid, well-structured Git commit message. Following clear conventions, like the Angular commit message format, ensures your commit history stays readable and easy to understand. It’s not just about neatness – using this format makes your changes easier to track, review, and even revert if necessary.

A good commit message typically consists of three parts: a header, a body, and a footer.

<header>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

The header is a must-have and needs to follow the Commit Message Header format.

The body is also required, unless it’s a "docs" commit. If you include a body, it should be at least 20 characters long and follow the Commit Message Body format.

The footer? Totally optional. But if you do use it, there’s a specific format for what it should include and how it’s structured.

Commit Message Header

The header is the most important part – it tells you what’s changed at a glance. This line is mandatory, and it should be short and sweet.

Format:
<type>(<scope>): <short summary>

<type>(<scope>): <short summary>
  │       │             │
  │       │             └─⫸ Summary in present tense. Not capitalized. No period at the end.
  │       │
  │       └─⫸ Commit Scope: animations|bazel|benchpress|common|compiler|compiler-cli|core|
  │                          elements|forms|http|language-service|localize|platform-browser|
  │                          platform-browser-dynamic|platform-server|router|service-worker|
  │                          upgrade|zone.js|packaging|changelog|docs-infra|migrations|ngcc|ve|
  │                          devtools
  └─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test

The <type> and <summary> fields are mandatory, the (<scope>) field is optional.

Type

This is where you tell the story of your commit. Is it a new feature? A bug fix? Some cleanup? The type lets everyone know what’s up at a glance, so your commits stay organized and easy to scan.

According to Angular commit convention, here are your main options::

  • fix: A bug fix
  • build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
  • ci: Changes to our CI configuration files and scripts (examples: CircleCi, SauceLabs)
  • docs: Documentation only changes
  • feat: A new feature
  • perf: A code change that improves performance
  • refactor: A code change that neither fixes a bug nor adds a feature
  • test: Adding missing tests or correcting existing tests

Scope

The scope gives context on which part of your project the commit touches. Usually, it’s the name of the npm package affected, so someone reading the changelog can quickly figure out what’s impacted.

Here’s a list of common scopes you might use:

  • animations
  • bazel
  • benchpress
  • common
  • compiler
  • compiler-cli
  • core
  • elements
  • forms
  • http
  • language-service
  • localize
  • platform-browser
  • platform-browser-dynamic
  • platform-server
  • router
  • service-worker
  • upgrade
  • zone.js

But, like with any rule, there are exceptions. Sometimes you’ll want to go beyond package names. For example:

  • packaging: For updates that tweak how npm packages are structured, like changing the public path or modifying the package.json.
  • changelog: When updating the release notes in CHANGELOG.md.
  • dev-infra: For changes in dev tools or infra, usually found in /scripts or /tools.
  • docs-infra: Related to docs for the Angular.io site, often in the /aio directory.
  • migrations: For updates to Angular’s ng update migrations.
  • ngcc: Changes to the Angular Compatibility Compiler.
  • ve: For work specific to the older ViewEngine.
  • devtools: Updates to browser extensions.

If none of the above fit, or if you’re making sweeping changes (like tests or refactors across the board), you can skip the scope altogether. For example:

  • test: test: add missing unit tests
  • docs: docs: fix typo in tutorial

By adding a scope, you give a little extra insight into the commit, making it easier for others to follow along—especially in big projects.

Summary

The summay is your quick, one-line summary of what the commit does. Keep it short and sweet. Here’s how to nail it:

  • Use the imperative and present tense: Say "fix" instead of "fixed" or "fixes."
  • No caps on the first letter — keep it casual!
  • Skip the period at the end — no need for extra punctuation.

This makes your commits easier to scan and follow, giving everyone a clear idea of what’s changed at a glance.

Header: "feat: add user profile editing functionality"

Type of Change: "feat" (feature addition)

Summary: "add user profile editing functionality"

In the example above, the header clearly identifies the commit as a feature addition aimed at introducing user profile editing functionality. This concise format ensures that the purpose of the commit is quickly understood without needing to delve into the details immediately.

Commit Message Body

The body of a commit message expands upon the details mentioned in the header. It includes:

Detailed Explanation: Provides a more comprehensive explanation of the changes made. This section should describe the specific modifications, additions, or deletions made to the codebase.

Reasoning: Explains the rationale behind the changes. It clarifies why the modifications were necessary or what problem they aimed to solve. This reasoning helps other developers understand the intent behind the code changes.

- Implemented frontend UI for editing user profiles
- Added backend API endpoints for profile updates
- Enhanced error handling for profile validation

Reason:
- Users need the ability to update 
  their profile information without 
  contacting support.

In our example, the body details the implementation of frontend and backend components for profile editing, alongside improvements to error handling. The reasoning behind these changes emphasizes enhancing user autonomy and reducing support overhead, aligning with user expectations and project objectives.

The footer of a commit message contains additional information about breaking changes and deprecations and is also the place to reference GitHub issues, Jira tickets, and other PRs (Pull requests) that this commit closes or is related to. It includes:

References: Links to related issues, tickets, or other commits that are relevant to the changes made in the commit. This provides a direct connection to the project's issue tracking system or documentation, allowing for easy navigation between code changes and their associated discussions or tasks.

Metadata: Any supplementary information that might be useful, such as dependencies affected by the change, configuration updates, or notes on compatibility issues. This section helps ensure that all relevant details are documented alongside the commit message.

for example:

BREAKING CHANGE: <breaking change summary>
<BLANK LINE>
<breaking change description + migration instructions>
<BLANK LINE>
<BLANK LINE>
Fixes #<issue number>

or

DEPRECATED: <what is deprecated>
<BLANK LINE>
<deprecation description + recommended update path>
<BLANK LINE>
<BLANK LINE>
Closes #<pr number>

Breaking Change section should start with the phrase "BREAKING CHANGE: " followed by a summary of the breaking change, a blank line, and a detailed description of the breaking change that also includes migration instructions.

Similarly, a Deprecation section should start with "DEPRECATED: " followed by a short description of what is deprecated, a blank line, and a detailed description of the deprecation that also mentions the recommended update path.

By structuring commit messages in this way, teams can maintain a clear and organized history of changes, facilitate effective code reviews, and streamline collaboration among developers. Clear commit messages also contribute to better project management and documentation, enhancing the overall maintainability and quality of the codebase over time.

Types of Commit Messages

When crafting commit messages, choosing the right type is key to keeping your project organized and making your commit history easy to navigate. Based on Angular conventions, there are 11 standard commit message types. You might also have custom types depending on your team's needs. Let’s break down each type with examples and explanations to show you when and how to use them.

Fix

The "fix" prefix in commit messages indicates corrections made to resolve bugs or issues within the codebase. When addressing bugs, the commit message should clearly describe the problem encountered and outline how it was resolved.

Example

fix: resolve pagination not updating correctly on user dashboard

- Adjusted pagination logic to update page numbers dynamically
- Fixed a conditional statement causing incorrect page count display

Fixes #456

In this example, the commit message succinctly describes the bug addressed and the specific changes made to resolve it. The inclusion of an issue reference (#456) provides traceability to the underlying problem, facilitating effective collaboration and code maintenance.

Feat

The "feat" prefix in commit messages signifies the introduction of new features or functionality to the codebase. When incorporating new features, the commit message should clearly articulate what the feature is and provide a brief rationale for its addition.

Example

feat: implement user profile picture upload feature

- Created frontend UI for image uploads
- Developed backend API endpoints for storage and retrieval
- Validated image size and format for user profiles

Reason:
- Enhances user engagement through customizable profiles

Refs #789

This example demonstrates an effective commit message for adding a new feature—user profile picture upload—by detailing the tasks completed without redundant explanations. The inclusion of a concise rationale and issue reference enhances transparency and facilitates understanding among team members reviewing the commit history.

build

The "build" type indicates changes related to the build system or external dependencies, such as modifications to build tools or updates to dependency configurations.

build(gulp): update Gulp configuration for ES6 support

- Updated Gulp plugins to support ES6 syntax in build tasks.
- Modified `gulpfile.js` to include Babel for transpiling ES6 code.
- Removed deprecated Gulp tasks and added new tasks for improved build performance.

BREAKING CHANGE: The build now requires Node.js version 12.0.0 or higher to support the updated Gulp plugins.
Please upgrade your Node.js version to avoid build issues.

In this commit example, the header specifies the type (build), scope (gulp), and a concise summary of the change. The body details the updates made to the Gulp configuration, including the addition of Babel for ES6 support and adjustments to build tasks. The footer includes a breaking change note, informing users of the required Node.js version to ensure compatibility with the updated build system.

cI

This is used for changes related to CI (Continuous Integration) configuration files and scripts, such as updates to CI pipelines, build scripts, or deployment configurations.

ci(circleci): update configuration for parallel job execution

- Modified `.circleci/config.yml` to include parallel execution of test jobs.
- Added new job definitions to split tests into smaller, faster-running groups.
- Updated CircleCI version to 2.1 to leverage the latest features and improvements.

Closes #789

Style

The "style" prefix in commit messages denotes changes made to improve code aesthetics, readability, or adherence to coding conventions without altering its functional behavior. This type of commit focuses on refining the presentation and structure of the codebase.

Example

style: update variable naming for clarity

- Renamed variables for improved readability
- Ensured consistent naming conventions across the module

No functional changes

In this example, the commit message demonstrates style-related updates aimed at enhancing code clarity through improved variable naming. By maintaining consistent naming conventions, the update supports better understanding and maintenance of the codebase without affecting its functional logic or behavior.

Refactor

The "refactor" prefix in commit messages indicates changes made to restructure existing code for improved readability, performance, or maintainability without altering its external functionality. This type of commit focuses on optimizing the internal structure or design of the codebase while preserving its observable behavior.

Example

refactor: optimize database query logic

- Simplified SQL queries for improved performance
- Implemented caching mechanism to reduce query times

No changes to external behavior

In this example, the commit message describes a refactor aimed at optimizing database query logic to enhance performance. By simplifying queries and implementing caching, the update improves the efficiency of data retrieval without introducing any observable changes to the application's external behavior.

Perf

The "optimize" prefix in commit messages indicates changes made to enhance the performance of the codebase. This type of commit focuses on improving efficiency, reducing resource consumption, or enhancing response times without altering its external behavior.

Example

optimize: improve image loading speed in gallery component

- Implemented lazy loading for images
- Optimized image compression for faster rendering

No functional changes

In this example, the commit message describes optimizations made to enhance the image loading speed within a gallery component. By implementing lazy loading and optimizing image compression, the update improves the overall performance of the application without affecting its external functionality.

Docs

The "docs" prefix in commit messages is used to indicate changes made exclusively to documentation within the codebase. This type of commit focuses on updating, adding, or improving documentation to enhance clarity, completeness, and understanding for developers and users alike.

Example

docs: update API usage guide

- Add examples for new endpoints
- Clarify authentication steps

Refs #789

In this example, the commit message details updates made to the API usage guide documentation. By adding examples for new endpoints and clarifying authentication procedures, these enhancements improve the comprehensibility of the documentation, aiding developers in effectively utilizing the API features. The reference (#789) links the commit to specific tasks or tickets, ensuring traceability and alignment with project requirements.

Test

The "test" prefix in commit messages indicates changes related to testing within the codebase. This type of commit is used when adding new tests, modifying existing ones, or fixing issues identified in the test suite to ensure code reliability and functionality.

Example

test: add unit tests for user authentication service

- Implemented tests for login and registration endpoints
- Validated error handling and edge cases

Refs #123

In this example, the commit message specifies the addition of unit tests for the user authentication service. The tests cover login and registration endpoints, focusing on error handling and edge cases to verify the service's functionality. The reference (#123) links the commit to specific tasks or tickets related to testing, ensuring comprehensive test coverage and adherence to project requirements.

Chore

The "chore" prefix in commit messages is used for routine tasks, maintenance activities, or other non-functional changes within the codebase. This type of commit encompasses updates that are necessary for project upkeep, organizational purposes, or general housekeeping.

Example

chore: update dependencies to latest versions

- Upgraded third-party libraries to address security vulnerabilities
- Ensured compatibility with latest platform updates

No functional changes

Remove

The "remove" prefix in commit messages indicates the deletion of code, files, or features from the codebase. This type of commit is used when removing unnecessary or deprecated elements to streamline the project and improve maintainability.

Example

remove: delete deprecated user authentication module

- Removed obsolete authentication logic
- Deleted unused files and dependencies

No functional changes

In this example, the commit message highlights the removal of a deprecated user authentication module and associated files. By eliminating outdated code and dependencies, the update simplifies the codebase and reduces maintenance overhead without altering the external functionality of the application.

Misc

The "misc" (miscellaneous) prefix in commit messages is used for changes that do not fit into standard categories but are defined by specific prefixes based on the team's conventions. This type of commit accommodates updates that may include unique or specialized modifications not covered by other commit message prefixes.

Example

misc: update README with deployment instructions

- Added detailed steps for deploying the application
- Included troubleshooting tips for common issues

No functional changes

In this example, the commit message demonstrates a miscellaneous update to the README file, focusing on providing comprehensive deployment instructions and troubleshooting guidance. This type of commit ensures that important project documentation remains accurate and accessible to developers and stakeholders, supporting effective project management and collaboration.

Best Practices for Writing Commit Messages

Be Descriptive

When crafting Git commit messages, being descriptive is crucial for clarity and understanding. Avoid vague statements like "fix bug" or "update code," which provide little context to fellow developers and future maintainers. Instead, clearly articulate what specific issue you addressed or what changes you implemented.

Example:

  • Good: "Fix bug in authentication logic causing null pointer exception"
  • Bad: "Fixed bug"

The good example specifies the exact nature of the bug and its impact, making it easier for others to comprehend the changes made. Descriptive commit messages not only streamline collaboration but also serve as valuable documentation for tracking changes over time.

Use Imperative Mood

In Git commit messages, it's standard practice to use the imperative mood when describing your changes. This convention aligns with how Git itself generates messages and enhances clarity and consistency across commits. Here’s why and how to apply it:

Example:

  • Good: "Fix typo in README"
  • Bad: "Fixed typo in README"

In the good example, "Fix typo" uses the imperative mood to succinctly state the action performed. This format is direct and efficient, making it easier for collaborators to understand the purpose of the commit at a glance.

Why Use Imperative Mood:

The imperative mood reflects commands or instructions, which is suitable for describing what the commit does rather than what it did. This approach keeps commit messages concise and focused on the action taken.

Keep Messages Short

Effective Git commit messages maintain brevity, especially in the header which should ideally be under 50 characters. This concise summary quickly conveys the essence of the change without overwhelming readers with unnecessary details. While the body can provide additional context, a succinct header ensures clarity and ease of understanding when reviewing commit history or pull requests.

Example:

  • Good: "Update API endpoint handling"
  • Bad: "Comprehensive update to improve error handling and response times for API endpoints across multiple modules"

In the good example, the header "Update API endpoint handling" efficiently communicates the specific focus of the commit, allowing collaborators to grasp the nature of the change at a glance. This practice enhances readability and facilitates effective collaboration within development teams.

Separate the Header and Body

Clear differentiation between the header and body of Git commit messages is crucial for improving readability and understanding. This practice enables developers to quickly grasp the essence of a commit at a glance while providing detailed context where necessary. Here’s why and how to implement this effectively:

Why Separate the Header and Body

The header acts as a concise summary, ideally under 50 characters, that succinctly describes the purpose of the commit. It serves as a quick reference for the nature of changes made without diving into specifics immediately. Conversely, the body elaborates on the header by providing comprehensive details. This includes specific tasks completed, the rationale behind the changes, and any relevant information that enhances understanding.

Best Practices for Separation

  • Utilize a blank line between the header and body to enhance visual clarity and organization.
  • Ensure the header is descriptive, focusing on the action taken (e.g., "Fix typo" or "Implement feature").
  • Use the body to delve into the specifics of what and why the changes were made, addressing issues or enhancing functionalities.

By maintaining this structured approach, your commit messages become both informative and concise, facilitating efficient collaboration and ensuring clarity in your project's development history.

Reference Relevant Issues

In Git commit messages, referencing relevant issues enhances traceability and collaboration within your project. When your commit addresses a specific issue or task, it's important to link it to the corresponding tracker or issue management system. Here’s how to effectively reference issues in your commit messages:

Why Reference Relevant Issues:

  • Traceability: Linking commits to issues provides a clear trail of development activities, making it easier to track changes over time.
  • Context: It helps team members understand the broader context and motivation behind each commit.
  • Integration: Many issue tracking systems automatically link commits to issues, streamlining project management and review processes.

Best Practices for Referencing Issues:

  • Use keywords like "Fixes #issue_number" or "Refs #issue_number" in the footer of your commit message.
  • Ensure the issue number is accurate and corresponds to the relevant task or bug.
  • Provide additional context if necessary, such as explaining how the commit resolves the issue or contributes to ongoing work.

By referencing issues in your commit messages, you not only maintain a well-organized development history but also foster transparency and accountability within your team. This practice ensures that all changes are systematically documented and aligned with project goals.

Common Pitfalls to Avoid

Being Too Vague

One of the common pitfalls in Git commit messages is being too vague. Messages like "fix bug" or "update code" lack specificity and fail to provide adequate context about the changes made. This vagueness can lead to confusion among developers and make it challenging to understand the purpose of each commit, especially when reviewing the commit history.

To avoid this pitfall, strive to be descriptive and precise in your commit messages. Clearly state what issue was addressed or what feature was implemented, and provide enough detail to convey the scope and impact of the changes. For example, instead of "update code," specify what aspect of the code was updated and why it was necessary, such as "Update authentication logic to fix session timeout issue."

By being explicit in your commit messages, you enhance transparency and facilitate smoother collaboration within your team. Each commit becomes a clear record of the actions taken, making it easier to track changes and maintain the integrity of your project's history.

Including Unrelated Changes

A common mistake in Git commit messages is combining unrelated changes into a single commit. It's important to focus each commit on a specific, clear purpose to maintain clarity and simplify the review process. When different changes are bundled together, it can be difficult to understand exactly what each commit is trying to achieve.

To prevent this issue, follow the principle of atomic commits—each commit should address one specific change or fix. This approach makes your commit history easier to follow and helps team members quickly grasp the purpose and impact of each modification.

For example, rather than mixing a bug fix with a feature addition in one commit, separate them into distinct commits. This ensures that each commit message accurately communicates the intention behind the changes made, promoting effective collaboration and efficient code maintenance practices within your development workflow.

Neglecting the Body

Neglecting to provide a detailed body in Git commit messages, especially for complex changes, can undermine the clarity and understanding of your commits. While it may seem quicker to skip this step, a well-written body is crucial for providing context, explaining the rationale behind the changes, and documenting any additional information that is essential for understanding the commit.

For complex changes, the body of the commit message serves as a valuable resource for future reference. It helps team members understand why specific decisions were made, how the changes address certain issues or requirements, and any potential impacts or considerations that should be taken into account.

To avoid this pitfall, take the time to include a descriptive body in your commit messages. Outline the steps taken, discuss any challenges encountered, and articulate the benefits of the changes made. This practice not only enhances communication within your team but also contributes to a well-maintained and informative commit history.

Conclusion

Effective Git commit messages are more than just notes in your version control history; they’re essential tools for clear communication among developers. By adhering to best practices and focusing on clarity and detail, your commit messages can greatly improve project management, collaboration, and code maintenance.

Semantic Versioning: Commit messages aren't just for keeping your history tidy; they also play a role in semantic versioning. Here’s how different types of commits align with versioning:

  • fix: This type addresses bugs in your codebase, corresponding to a PATCH update in semantic versioning.
  • feat: This type introduces new features, which aligns with MINOR updates.
  • BREAKING CHANGE: This label, found in the body or footer of a commit, signifies major changes that impact the API, matching MAJOR version updates. A BREAKING CHANGE can appear with any commit type.

Other commit types like chore:, docs:, style:, refactor:, perf:, and test: are also useful, as recommended by conventions like Angular’s.

Streamlining the Process: You don’t have to memorize these formats or keep a cheat sheet handy all the time. Tools like Commitlint and Commitizen can automate this process for you. They help ensure your commits are formatted correctly by prompting you to specify the type, scope, summary, body, and footer before each commit. It’s like having a personal assistant for your version control!

By integrating these practices and tools, you'll enhance both your development workflow and your project’s overall maintainability.