The Three‑Commit Story: Turning Messy Coding Sessions into Clear Progress
How to use three deliberate commits as tiny, self‑contained stories that bring structure to your coding sessions, improve Git history, and turn every block of work into visible progress.
Introduction
You sit down to code for “just an hour.” Three hours later, you’ve changed ten files, tried three different approaches, fixed an unrelated bug, and your Git history is a graveyard of wip, oops, and fix stuff commits.
The code might work, but your progress is hard to see. Reviewing what happened is painful. Future‑you (or your teammates) will have no idea why things changed the way they did.
There’s a better pattern: the three‑commit story.
This is a simple practice that turns fuzzy goals into tiny, trackable units of progress—using just three deliberate commits per focused session. It helps you:
- Avoid sprawling, meandering coding sessions
- Keep your Git history readable and reviewable
- Turn every block of work into a meaningful, documented story
Let’s break down how it works.
What Is a Three‑Commit Story?
A three‑commit story is a tiny, self‑contained unit of progress that you can complete in a single focused session (typically 30–120 minutes).
It has three characteristics:
- Small scope – one clearly defined improvement (e.g., “add search to the products list,” “fix pagination bug,” “extract email sending to a service”).
- Three intentional commits – you structure your work into three steps, each captured as a commit.
- Readable narrative – the three commits together tell a story of what you did and why.
Instead of approaching a task as “implement search,” you approach it as:
- Plan & scaffold: Set up structure, tests, and placeholders.
- Implementation: Make it actually work.
- Cleanup & refactor: Polish, simplify, and document.
Each of these becomes one commit, forming a compact, understandable story in your Git history.
The Three Deliberate Commits
A three‑commit story follows a simple template. You can adapt the labels, but the flow remains the same.
1. Plan / Scaffold Commit
Goal: Prepare the ground without fully implementing the feature.
Typical changes:
- Add or update tests that express the desired behavior
- Introduce new files, classes, or functions as placeholders
- Add TODO comments or stubs where logic will go
- Wire up basic routing or configuration
Example commit message:
feat: scaffold product search endpoint - add /products/search route - define ProductSearchRequest and response DTO - add failing integration test for search by name
Why it matters:
- Forces you to clarify what you’re about to build
- Gives you a clear target (tests, types, or contracts)
- Keeps implementation separate from design and setup
2. Implementation Commit
Goal: Make it work. This is where the main logic lands.
Typical changes:
- Implement the previously stubbed methods
- Make tests pass
- Add essential error handling and happy‑path behavior
Example commit message:
feat: implement product search by name - query products by name (case-insensitive) - support partial matches with LIKE - ensure existing filters still apply
Why it matters:
- Keeps the “meaty” change in one place
- Easier to review and debug because intent is clear
- If something breaks, this is the first commit you inspect
3. Cleanup / Refactor Commit
Goal: Polish and stabilize what you just built.
Typical changes:
- Refactor for readability and maintainability
- Remove duplication or dead code
- Improve naming and structure
- Update docs or comments
Example commit message:
refactor: simplify product search query - extract query builder to ProductSearchSpecification - rename params for clarity - add comments for search behavior
Why it matters:
- Keeps refactors separate from behavior changes
- Makes diffs smaller and more meaningful
- Encourages you not to skip cleanup because it has its own slot
You end the session with a three‑act story: scaffold → implement → refine.
Writing Clear, Action‑Oriented Commit Messages
Three‑commit stories work best when each commit message is short, specific, and action‑oriented.
Use this simple structure:
- Summary line (max ~72 characters)
- Optional body for details, each bullet or line explaining what and why
Examples:
feat: add optimistic locking to order updates - prevent double-submit from overwriting changes - surface concurrency error to the UI
fix: handle empty query string in product search - return 400 instead of 500 on invalid query - add regression test for empty search
Guidelines:
- Start with a verb:
add,fix,remove,refactor,document,rename. - Be concrete:
implement product search pagination, notmisc changes. - Use the body to explain why for non‑obvious decisions.
The result is a Git history that reads like a sequence of clear, small actions.
Using Consistent Prefixes to Signal Intent
Consistent prefixes make patterns jump out when scanning history. Popular choices include:
feat:– new feature or behaviorfix:– bugfix or defect resolutionrefactor:– code changes that don’t change behaviordocs:– documentation onlytest:– test‑related changeschore:– tooling, config, or maintenance
In the three‑commit story, you might see patterns like:
feat: scaffold product searchfeat: implement product search by namerefactor: simplify product search query
Or for a bugfix:
test: add regression test for pagination bugfix: correct page offset calculationrefactor: extract pagination logic into helper
These prefixes:
- Make it easy to filter commits by type
- Help reviewers understand the purpose of each change
- Enable automation tools to generate meaningful changelogs
Treating Git History as Documentation
When you adopt three‑commit stories, your Git history stops being a junk drawer and starts becoming usable documentation.
Benefits:
Easier Code Reviews
Reviewers can:
- Walk through the story commit by commit
- See the design, implementation, and cleanup stages separately
- Comment on smaller, focused diffs
Faster Debugging
When a bug appears, you can:
git blamea line and see a clear commit message- Inspect the three‑commit story around that area
- Understand why a change was made, not just what changed
Clearer Knowledge Sharing
New team members can read history to learn how the system evolved:
- “Oh, this is when we added optimistic locking.”
- “Here’s how we approach refactors without changing behavior.”
Each three‑commit story is a tiny chapter in your project’s documentation.
Turning Commit History into Automation Fuel
Well‑structured commit history is not just nice for humans—it’s gold for automation.
With consistent prefixes and clear messages, you can wire up tools like GitHub Actions, GitLab CI, or other pipelines to:
- Generate changelogs from
feat:andfix:commits - Build API docs when
docs:or certainfeat:commits land - Draft release notes automatically from commit history
For example, a release pipeline might:
- Scan commits since the last tag
- Group them by type (
feat,fix,refactor) - Produce a markdown changelog section for the next release
Suddenly, your disciplined three‑commit stories feed directly into your project’s communication and release process.
Making Three‑Commit Stories a Habit
Habits stick best when they’re simple and repeatable. Here’s how to build this one.
1. Start With a Tiny Scope
Ask yourself before coding:
What’s a unit of progress I can complete in one focused session?
If it feels big, slice it smaller. Aim for stories you can finish without feeling rushed.
2. Name Your Three Commits Up Front
Before you start, jot down something like:
feat: scaffold user password resetfeat: implement password reset email flowrefactor: tidy password reset handlers
This gives you a mini roadmap and makes it easier to stop scope creep.
3. Resist Mixing Stages
While implementing, you’ll notice refactors. Instead of doing them immediately:
- Drop a TODO comment
- Or note them in your session notes
- Handle them in the third, dedicated cleanup commit
This keeps each commit focused and easier to review.
4. Practice, Don’t Police
This is a technique, not a religion. Sometimes you’ll need:
- A fourth commit to fix a test
- A single small commit for a trivial change
Use the three‑commit story as a guiding pattern, not a rigid rule. The goal is clarity and progress, not perfection.
Conclusion
Messy coding sessions happen when goals are fuzzy and changes blend together. The three‑commit story gives you a simple structure:
- Plan & scaffold the change
- Implement the behavior
- Cleanup & refactor the result
Combined with clear, action‑oriented commit messages and consistent prefixes, each session becomes a small, well‑told story in your Git history.
Over time, you’ll notice:
- Less chaos in your branches
- Faster, clearer code reviews
- Easier debugging and onboarding
- A Git history that doubles as living documentation
Try it in your next session: pick a tiny goal, sketch your three commits, and see how it changes the way you work. One small story at a time, you’ll turn messy progress into clear, visible momentum.