DEV Community

Cover image for Git Practices for Critical Production-grade Codebases
Ashwin Gopalsamy
Ashwin Gopalsamy

Posted on • Originally published at ashwingopalsamy.substack.com

Git Practices for Critical Production-grade Codebases

TL;DR: Master Git with best practices for commits, branches, and PRs in production-grade systems. Learn to keep codebases clean, traceable, and reliable using tools like Commitlint.

Git is the backbone of every software project. Whether you're debugging a critical issue, implementing a feature, or deploying to production, Git holds the story of your project’s evolution. But that story can get messy if we don’t follow some structure. Trust me, I’ve seen (and written) my share of cryptic commit messages and tangled branches, and it never ends well.

Working in highly critical, production-grade FinTech systems, I’ve learned that Git workflows are more than just a convenience—they’re a lifeline. Clean commits, branches, and pull requests (PRs) don’t just make life easier; they protect the stability of the systems we build, ensuring traceability and reducing risk in environments where the stakes couldn’t be higher.

Here’s a guide to Git best practices that I aim to follow (or at least aspire to), with tools to help you enforce these practices without adding too much overhead.


Commits: The Backbone of Your Codebase

Your commit history is like a logbook. It should tell you exactly what happened and why, in a way that’s easy to follow. In production-grade systems, this clarity isn’t optional—it’s critical.

1. Write Atomic Commits

An atomic commit focuses on one thing—fixing a bug, adding a feature, or refactoring code. This ensures every commit is clear, self-contained, and easy to understand. It also makes debugging and rollbacks safer.

Example:

  • Good:
   feat: add endpoint for user account details
   fix: handle timeout in transaction service
Enter fullscreen mode Exit fullscreen mode
  • Bad:
   misc: fix bugs and add features
Enter fullscreen mode Exit fullscreen mode

When I’m in a hurry, it’s tempting to lump everything into one commit, but I always regret it later when debugging becomes a nightmare.


2. Use Descriptive Commit Messages

Your commit message should explain what changed and, if needed, why. Following a consistent format helps everyone on the team (including your future self) understand what’s going on.

Structure:

<type>(<scope>): <subject>
<BLANK LINE>
<body (optional)>
Enter fullscreen mode Exit fullscreen mode

Examples:

  • fix(auth): resolve token expiration issues in login
  • feat(api): add batch endpoint for transaction summaries

These messages don’t just help during reviews—they’re lifesavers when digging through logs or debugging an issue six months down the line.


3. Automate Commit Linting

No matter how disciplined we are, it’s easy to slip up. That’s where Commitlint comes in. It’s a lightweight tool that ensures your commit messages follow a defined convention, like Conventional Commits.

Tools for Commitlint:

  1. Commitlint by Conventional Changelog

    This is one of the most popular Commitlint tools. It’s simple, extensible, and works seamlessly with Husky to enforce commit message rules during pre-commit or pre-push hooks.

  2. Commitlint for Conventional Commits

    Written in Golang, this lightweight Commitlint tool is fast and easy to set up for smaller teams or those already using Go. It’s perfect if you prefer tools that feel native to your tech stack. My personal favorite.

  3. Conventional Commits Specification

    A useful guide to the conventions enforced by these tools.

Setting up Commitlint is straightforward, and the long-term benefits—clear commit messages, consistent history—are well worth the effort.


Branches: Organized and Traceable

In highly critical systems, branch organization is non-negotiable. It’s not just about avoiding confusion—it’s about making sure work can be traced back to its purpose, especially in microservices architectures where each service lives in its own repository.

1. Follow a Consistent Naming Convention

A good branch name starts with the task or issue identifier, making it easy to see what the branch is for. I always use this format:

Format:

<JIRA-ticket-ID>-<type>-<short-description>
Enter fullscreen mode Exit fullscreen mode

Examples:

  • JIRA-5678-fix-transaction-timeout
  • JIRA-1234-feature-add-batch-processing

This convention has saved me—and my team—countless hours when tracking work across multiple repositories.


2. Keep Branches Short-Lived

The longer a branch stays open, the more likely it is to diverge from the base branch. I aim to merge branches into main or develop frequently, keeping integration smooth and reducing conflicts.


3. Rebase for a Clean History

Rebasing instead of merging keeps your branch history linear, which is much easier to follow during debugging or reviews.

Example Workflow:

git checkout JIRA-5678-fix-transaction-timeout
git pull --rebase origin main
Enter fullscreen mode Exit fullscreen mode

Rebasing has saved me from so many messy histories, but I’m always careful not to rebase shared branches like main.


Pull Requests: The Gateway to Collaboration

Pull requests are where collaboration happens. In highly critical systems, they also serve as an essential checkpoint to catch mistakes before they make it to production.

1. Use Clear and Structured PR Titles

A PR title should be concise but informative. I use this format to keep things consistent and easily traceable:

Format:

[JIRA-ticket-ID] <Type>: <Short Description>
Enter fullscreen mode Exit fullscreen mode

Examples:

  • [JIRA-5678] Fix: Handle transaction timeout edge cases
  • [JIRA-1234] Feature: Add bulk processing for transactions

2. Write Descriptive PR Descriptions

A good PR description provides enough context to help reviewers understand what’s changing and why. I try to answer three key questions:

  1. What changed?
  2. Why was this change made?
  3. Does it introduce any risks or side effects?

Example:

### What
Added a batch endpoint for processing transaction summaries.

### Why
This improves efficiency for bulk transaction reconciliation.

### Impact
- Adds a new API endpoint.
- No breaking changes.
- Includes unit and integration tests.
Enter fullscreen mode Exit fullscreen mode

3. Keep PRs Small and Focused

Large PRs are overwhelming to review and prone to mistakes. I aim to keep PRs focused on a single feature, bug, or task to make reviews faster and more effective.


4. Use Checklists and Labels

Checklists ensure that every step is complete before merging:

  • [ ] Unit tests added
  • [ ] Integration tests verified
  • [ ] Documentation updated

Labels like feature, fix, or hotfix also help prioritize reviews.


Why These Practices Matter

In highly critical, production-grade FinTech systems, precision isn’t optional. I’ve seen how poor Git practices can snowball into major issues—long debugging sessions, delayed releases, or even customer-facing outages. Clean commits, structured branches, and clear PRs aren’t just best practices—they’re safeguards for the stability and trustworthiness of the systems we build.

By following these practices:

  • Debugging in production becomes faster and more efficient.
  • Collaboration is smoother because everything is easy to trace.
  • Compliance and audits are simpler, with clear histories and well-documented changes.

If you’ve got your own favorite tools or Git horror stories, let’s swap notes. The best practices we share today could save someone a lot of time (and stress) tomorrow.

Glad you like it. May the code be with you. <3

My Social Links: LinkedIn | GitHub | 𝕏 (formerly Twitter) | Substack | Dev.to | Hashnode

Top comments (1)

Collapse
 
bntstr profile image
Bntstr

Nice post