How I Messed up a PR—and How You Can Write Better Git Commit Messages


June 12, 2025 · ~1,000 words
The Day I Deleted My Own PR
It was a bright Thursday morning when I decided to add password hashing to our FastAPI backend. I wrote a quick utility in utils/password.py
, updated the signup endpoint, tweaked the README and pushed my branch:
git add utils/ api/ README.md requirements.txt
git commit -m "Defined a function to hash a password using the bcrypt algorithm."
git push -u origin hashpassword
Moments later, I opened the pull request and froze. My commit message was a long, rambling sentence in past tense and I’d split related tweaks into three tiny commits:
Add bcrypt password utils and Google ID token verification
Update README with X changes
Removed google.py
On GitHub, the PR looked messy. The commit log was cluttered, the message was wordy and I had even included a stray endpoint file I didn’t intend to ship. In a moment of panic, I deleted the PR, determined to start over with a clean slate.
Little did I know, that “fresh start” would teach me the single most important skill in collaborative Git: how to write clear, consistent commit messages so that future-me and your teammates never need to delete a PR again.
Why Commit Messages Matter
A good commit message is more than a note to yourself. It’s a promise to everyone else on the project:
Context for Reviewers
A concise message helps reviewers understand what you changed and why before they dive into the diff.Readable History
Ten commits titled “Fix stuff” teach you nothing when you’re bisecting a bug six months later.Automation & Tooling
Structured messages fuel changelog generators, semantic-release workflows and commit-lint checks.
Nail your commit messages up front and you’ll avoid awkward PR deletions, rebase meltdowns and frantic late-night force-pushes.
My Fresh Start: The One-Commit PR
Here’s the “start-from-scratch” workflow I used to rescue my feature:
Kill the old branch
git branch -D hashpassword
Reset
main
to match origingit checkout main git fetch origin git reset --hard origin/main
Create a brand-new branch
git checkout -b add-password-hashing
Re-apply only the polished changes
utils/api.py
with bcrypt helperscleaned-up
README.md
andrequirements.txt
Stage just those files
git add utils/api.py README.md requirements.txt
Write a single and crisp commit message
git commit -m "Add bcrypt password-hashing utility and update docs"
Push & open a new PR
git push -u origin add-password-hashing
Voilà—one branch, one commit, one clear message. No PR deletions required.
Anatomy of a Great Commit Message
Whether you’re on a solo side-project or a 50-person team, these principles will level up your Git game:
Keep the subject ≤ 50 characters
Shorter is sweeter. It fits in logs and GUI displays without wrapping.Use imperative present tense
Write it like a command that Git will execute when applied:| Bad (past tense) | Good (imperative) | | --- | --- | | Defined a function to hash passwords | Add function to hash passwords | | Updated README installation section | Update README installation steps | | Removed deprecated Google module | Remove deprecated Google module |
Separate subject from body with a blank line
If you need more context, follow the subject with a paragraph explaining the why:Add bcrypt password-hashing utility and update docs Use bcrypt with a work factor of 12 for stronger security. Clarify installation steps in README to include the new dependency.
Wrap the body at ~72 characters
Keeps your text readable in terminals and code-review tools alike.No trailing period on the subject
That little dot wastes precious characters and can look inconsistent.
Leveling Up: Conventional Commits
If your team wants even more structure—automated changelogs, semantic-release, commit-lint ,you can adopt the Conventional Commits spec:
<type>(<scope>): <subject>
<body>
<footer>
type:
feat
(new feature),fix
(bug fix),docs
,style
,refactor
,perf
,test
,chore
scope (optional): area of the codebase, e.g.
auth
,db
,api
subject: imperative, ≤50 characters
footer: references (
Closes #123
), breaking changes
Example
feat(auth): add bcrypt password-hashing utility
Use bcrypt with strength 12 to protect user passwords
and update README with installation instructions.
Closes #42
Conventional Commits empower tools to:
Auto-generate your CHANGELOG.md
Bump versions semantically (
feat
→ minor,fix
→ patch)Enforce commit-message policies via CI
Putting It All Together
Before you hit git commit
, run through this checklist:
Branch
Create a dedicated feature branch—never commit directly tomain
.Stage thoughtfully
Onlygit add
the files relevant to this logical change.Write your subject
Imperative, present tense
≤50 characters
No trailing period
Add a body (if needed)
Explain why you made this change, not what you changed.Use Conventional Commits (optional)
Prefix withfeat(scope):
orfix:
if your project uses them.Rebase or merge
main
frequently
Keep your branch up to date to avoid conflict headaches.Squash related work
Collapse WIP commits or tiny fix-ups into a single, unified commit before opening your PR.
Final Thoughts
Deleting that first PR stung but the clean branch and well-crafted commit message that replaced it saved hours of confusion for both me and my reviewers. Today, I treat every commit as an entry in our project’s story-clear, concise and purposeful.
Next time you find yourself staring at a messy PR, remember: it’s not about deleting work, it’s about writing it right. Commit with intention and your future self will thank you.
Happy coding (and committing)! 🚀
Subscribe to my newsletter
Read articles from Ganiyatu Sanuusi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Ganiyatu Sanuusi
Ganiyatu Sanuusi
Tech with Ghaniya is a space where I share real-world solutions to tech problems I’ve faced — and go further by offering practical tips, tutorials, and tools to help others learn, build, and grow. From software development to everyday tech challenges, if it helps someone level up, it’s worth writing about