Git Best Practices – How to Write Meaningful Commits, Effective Pull Requests, and Code Reviews
As developers, we spend a significant amount of time working with Git – writing commit messages, submitting pull requests, reviewing code, and merging changes. While the fundamentals of Git are relatively straightforward, it takes some experience and discipline to master the art of collaboration via a distributed version control system.
In this guide, we‘ll cover some Git best practices I‘ve learned over the years that help keep codebases clean, organized, and easy to maintain. We‘ll focus on three key areas – writing meaningful commit messages, structuring effective pull requests, and conducting productive code reviews.
Whether you‘re a seasoned developer or just starting out, following these practices will help you communicate better with your team, reduce errors and code smells, and ultimately ship higher quality software. Let‘s jump in!
Writing Meaningful Commit Messages
Commit messages are the public record of the changes made to a codebase. They let your teammates and your future self understand the context and reasoning behind each modification. Here are some tips for writing clear, meaningful commit messages:
Use the Imperative Mood
Write your commit messages as if you are giving a command or instruction. Instead of writing in past tense like "Fixed bug" or "Added feature", use the imperative tense: "Fix bug" or "Add feature".
Some other examples:
- Refactor subsystem X for readability
- Update getting started documentation
- Remove deprecated methods
- Release version 1.0.0
A properly formed Git commit subject line should always be able to complete the following sentence:
- If applied, this commit will [your subject line here]
For example:
- If applied, this commit will refactor subsystem X for readability
- If applied, this commit will update getting started documentation
- If applied, this commit will remove deprecated methods
- If applied, this commit will release version 1.0.0
Writing in the imperative makes it clear what applying the commit will do, as opposed to what you did.
Keep It Concise
Aim to keep the commit title (the first line of the message) to 50 characters or less. This makes it easy to quickly scan a list of commits. If you need to provide more context, add a blank line and write a more detailed explanation in the body:
Add customer signup API
- Creates a new Customer resource when a valid email and password are POSTed to /signup
- Returns a 200 OK response with the customer ID on successful creation
- Returns a 400 Bad Request when the email is already in use or the password does not meet complexity requirements
- Sends a welcome email to the new customer after saving to DB
The title communicates the key change, while the body provides additional details that are useful for future reference but not necessary to understand what the commit does.
Explain the Why, Not Just the What
A diff will tell you what changed, but only the commit message can properly convey why. Focus on explaining the reasons and intent behind the change, rather than just summarizing the code.
For example, instead of just writing:
Change background color to blue
Provide more context:
Change background color to increase contrast ratio
The current gray background (#CCC) fails WCAG 2.0 level AA contrast ratio requirements when used with white text. Switching to a darker blue (#036) increases the contrast to an acceptable level (7.2:1).
Now future maintainers (including yourself) will understand not just what was changed, but why it was changed and how it improves the codebase.
Reference Related Issues
If the commit is related to a particular issue, bug report, or feature request, reference it using a hashtag and the issue number:
Fix SQL injection vulnerability in login form (#123)
Most Git hosting services will automatically link the reference to the issue tracker, making it easy to get more context. Some systems will also auto-close issues when a commit message references them with certain keywords like "fixes" or "closes".
Provide Links to Additional Resources
If there are relevant pull requests, stack overflow answers, mailing list threads, slack conversations, or other resources that would be useful for understanding the commit, link to them in the message body:
Implement new OAuth 2.0 flow
See RFC 6749 for details on OAuth2 spec:
https://tools.ietf.org/html/rfc6749
Github Issue: https://github.com/org/repo/issues/123
Related PR: https://github.com/org/repo/pull/124
This helps keep discussions and decisions connected to the actual code changes and makes it easier for others to dig deeper if needed.
Use a Consistent Style
Establish a consistent commit message style and stick to it. This could include things like:
- Prefixing the message with the type of change (feat, fix, docs, style, refactor, test, chore)
- Including an emoji to visually categorize commits (? for breaking changes, ? for bug fixes, ✨ for new features, ? for documentation, etc)
- Wrapping the message body at 72 characters for better readability
- Using a tool like commitizen to generate properly formatted messages
Having a standardized format makes it easier to scan the commit log and understand the different types of changes. Many teams adopt the Conventional Commits specification which provides a structured way to write messages.
Creating Effective Pull Requests
Pull requests (or merge requests in GitLab) are the mechanism for proposing and collaborating on changes to a codebase. A good pull request is focused, clearly explained, and easy for reviewers to understand and test. Here are some tips:
Keep It Small and Focused
Resist the urge to include multiple unrelated changes in a single pull request. Instead, create separate PRs for each feature or fix. This makes it easier for reviewers to understand the changes and reduces the risk of unintended side effects.
If you find yourself with a PR that has dozens of commits and touches hundreds of files, it‘s probably too big. Try to break it down into smaller, logical pieces that can be reviewed and merged independently.
Write a Descriptive Title and Description
The PR title should clearly and concisely summarize the change, similar to a good commit message. The description should explain the motivation for the change, any important context or background info, and instructions for testing or validating the changes.
Include links to related issues, design documents, or user stories to help reviewers understand the bigger picture. If the PR is a work in progress (WIP), indicate that in the title so reviewers know not to merge it yet.
Here‘s an example of a well-structured PR description:
## Problem
The current implementation of the search API is not paginating results, causing performance issues when returning large datasets.
## Solution
- Add pagination parameters (`page` and `per_page`) to the `/search` endpoint
- Default to returning 20 items per page, with a max of 100
- Include total count and links to next/previous pages in response
## Testing
- Wrote unit tests for new pagination logic
- Manually verified that results are correctly paginated for various queries
- Ran load tests to ensure pagination improves response times for large result sets
## References
- Issue #123
- API design doc (link)
- Performance testing results (link)
Provide Context with Screenshots and GIFs
A picture is worth a thousand words, especially when it comes to demonstrating UI changes or new features. Include before/after screenshots, GIFs, or short videos to show the effects of the changes.
This is especially useful for front-end code reviews, where reviewers can quickly see how the UI looks and behaves without having to pull down the code and run it locally.
Tools like Kap, Licecap, and Giphy Capture make it easy to record your screen and attach the resulting GIF to the PR description or comments.
Explain Anything Unusual
If the changes include anything out of the ordinary or experimental, call it out in the description and explain the reasoning behind it. This could be things like:
- Using a new library or framework
- Deviating from established coding conventions
- Ugly workarounds for browser bugs
- Commented out code
- Expensive operations or database queries
- Complex regular expressions
Don‘t leave it to reviewers to figure out why something looks strange – be proactive in explaining and justifying unconventional code.
Make Reviewing and Testing Easy
Include clear instructions in the description for how to set up the development environment, run tests, and manually verify the changes. If the PR includes UI changes, provide a link to a staging environment or deploy preview.
Consider adding task lists to the description to guide reviewers through the different parts of the PR:
## Testing Checklist
- [ ] Run unit tests
- [ ] Verify pagination works for small and large result sets
- [ ] Check that response includes total count and next/prev links
- [ ] Ensure max `per_page` limit is enforced
- [ ] Test different combinations of `page` and `per_page` params
Reviewers can check off items as they go and see at a glance what still needs to be tested.
If the codebase has a CI/CD pipeline, make sure all tests and checks are passing before requesting review. This saves time and back-and-forth if the code isn‘t even in a working state.
Conducting Effective Code Reviews
Code reviews are a crucial step in maintaining code quality and catching bugs before they make it to production. They‘re also an opportunity for knowledge sharing and mentorship. Here are some tips for being an effective code reviewer:
Review Promptly
Don‘t let PRs languish for days or weeks without any feedback. Aim to do an initial review within 1-2 business days of being assigned (assuming the PR is reasonably sized).
If you don‘t have time to do a full review right away, leave a comment acknowledging receipt of the PR and letting the author know when you expect to get to it. This helps the author plan their work and reduces the chances of merge conflicts piling up.
Test the Changes Locally
Don‘t just review the code in the abstract – pull down the branch and run it locally to verify it actually works as intended. Test different scenarios and edge cases to catch any bugs or unhandled exceptions.
Follow the manual testing steps provided in the PR description to ensure you‘re checking all the important functionality. If the steps are unclear or missing, ask the author to add them.
Give Constructive Feedback
Be specific, objective, and kind in your feedback. Instead of just saying "this is wrong", explain what the problem is and offer suggestions for improvement:
❌ "This code is a mess, what were you thinking?"
✅ "This function is doing too many things – could we break it up into smaller, single-purpose functions to improve readability and testability?"
❌ "Why aren‘t you using Library X here? You clearly don‘t know what you‘re doing."
✅ "Have you considered using Library X for this? It has some built-in utilities that could simplify the implementation. Here‘s an example: …"
❌ "We don‘t do it this way here, change it to match our style."
✅ "We typically use snake_case for variable names in this codebase to match the Python style guide (PEP8). Could you update these to follow that convention? Let me know if you have any questions!"
Remember that the goal is not to nitpick or show off how much you know, but to help the author improve their code and grow as a developer.
Praise Good Work
Don‘t just point out the problems – acknowledge and appreciate the things the author did well! Call out clever solutions, well-written tests, thoughtful comments, and other positive aspects of the code.
Especially if you‘re reviewing code from a junior dev or someone new to the team, a little encouragement and recognition can go a long way in building their confidence and motivation.
Differentiate Between Blockers and Nits
Not all feedback is equally important. Distinguish between issues that are true blockers (bugs, security holes, performance problems) and nitpicks or matters of opinion (style, naming, etc).
Use a system of categorizing your comments to help the author prioritize:
- Blocker: Must be fixed before merging
- Major: Should be fixed before merging, but not a deal-breaker
- Minor: Can be fixed in a follow-up PR
- Nit: Subjective opinion or preference
Try to be judicious with minor and nit-level comments. Too much bikeshedding over trivial issues can be demotivating and delay shipping value.
Approve or Request Changes
Once you‘ve finished reviewing the PR, either approve it or request changes. Don‘t leave the author hanging by not indicating the next steps.
If you‘re approving, leave a quick comment like "LGTM!" (looks good to me) or "+1". If you‘re requesting changes, make it clear what needs to be addressed before you‘ll approve.
Some teams require multiple approvals before a PR can be merged – make sure you understand and follow your team‘s policies.
Follow Up
A code review doesn‘t end when the PR is merged. Follow up to make sure the requested changes were actually made and that the feature is working as expected in production.
If there are any issues or bugs, file new tickets and work with the author to get them fixed promptly. Code reviews are a continuous process of collaboration and iteration, not just a one-time gate.
Conclusion
Writing good commit messages, PRs, and code reviews is a skill that takes practice and discipline to master. By following the tips and best practices outlined in this guide, you can level up your Git game and become a more effective collaborator and communicator.
Remember, the goal is not just to ship code, but to build a shared understanding and ownership of the codebase as a team. Every commit message, PR description, and review comment is an opportunity to make the codebase a little bit better and to help your teammates grow.
So the next time you‘re about to git commit -m "whatever"
, take a moment to think about how you can use that tiny text box to add real value and clarity to your project. Your future self (and your teammates) will thank you!