Atomic: of or forming a single irreducible unit or component in a larger system.
Also available on my blog.
Knowing VS Actually Knowing
I remember when my first mentor told me about Test-Driven Development. A great methodological approach to software engineering. It took me just a few minutes to understand what it was and how it worked.
Then I spent the next six months actively not practicing it. TDD is hard when you start, so you just... don't.
Half a year later, I was hitting my head against a wall trying to build a new feature. I forced myself for the first time to actually do TDD by the book. The wall that was blocking my progress disappeared instantly.
In a previous article, I explained how many managers and tech leads often know exactly how to make our industry better... yet don't. There is always a good reason to not do things right. And somehow we still end up surprised when we're doing things wrong.
This long introduction is here to illustrate a point: you can know what you should do, but you might not know how important it is to actually do it. So many people out there, just like I did before, know how TDD is great... yet still don't use it.
The simplest concepts can often completely change the way you work... if you would only apply them. Introducing: the atomic git commits.
What's an atomic git commit?
Working with atomic git commits means your commits are of the smallest possible size. Each commit does one, and only one simple thing, that can be summed up in a simple sentence.
The amount of code change doesn't matter. It can be a letter or it can be a hundred thousand lines, but you should be able to describe the change with one simple short sentence.
Ideally, you also want your test suite to be in the green when you commit. Your changes might be "atomic", i.e the smallest possible, but they should also be "complete", which means your test suite always follow through.
As small as possible, but complete: this is an atomic git commit.
Why should you write atomic git commits?
There are a few great advantages to practicing atomic git commits, and we'll briefly detail them. But the last one really is the most important. It might completely change the way you approach your work, increase your productivity by an order of magnitude, and make your job much more enjoyable.
Reason number 1: An atomic change is a reversible change
We all know this simple truth about software: the requirements are always changing. By writing atomic git commits, we allow ourselves to revert any changes by a simple commit revert. This already increases your productivity tremendously.
Reason number 2: A clean git history
When shit hit the fan, a clean git history means the difference between pain and salvation. It's like insuring your house: seems useless, until there's a fire.
Reason number 3: Pull requests are much easier to review
Your team will absolutely love you for this.
Reason number 4: A much, much better workflow
This is by far the most important reason to practice atomic git commits: it completely alters the way you approach problem-solving.
If you're like me, you might have a tendency while developing a feature, to just... do it. Entirely.
Then you realize how you did not think everything through.
You need to change more that you expected. Some edge cases aren't taken into account. You broke some unrelated tests, they need fixing. Soon, you end up in a maze of your own making. You're lost. Your head hurts. You can't make any progress without being entirely focused.
Now, this is the wrong way to do things. And worse, you already know the right way, because it's so obvious.
The well-known method to complete a big, complex task: cut it down into smaller, manageable, tiny steps. Each step -its own simple problem to solve. This is obvious advice that you probably already heard many times... But are you actually practicing it in your daily job?
Well, here's a great way to actually practice it: write atomic git commits.
By forcefully working in atomic commits, you're approaching the work the right way, by simplifying it into smaller steps. After all, simplifying complexity is the very core of our job. So why aren't we always consciously doing it?
Of course this advice might sound obvious. But if my past experience proves anything, it's that the obvious really bears repeating, and even more importantly, it bears practicing.
Make your work simpler, better, more manageable, and most importantly: make it easier. Take small steps. Write small commits. Atomic commits. You will love them.
Top comments (61)
Recently I’ve started to take this approach and I feel much better at work. I used to get confused with what I was doing after a big pause like lunch or a meeting, because I was never sure about what was already done and what needed more attention in the code, and it was hard to come up with a plan. In the end, I had to review everything I’ve done to keep it going.
The thing that was preventing me to commit — and maybe this might happen to other folks — is that I was never sure if what I did was right before I saw the whole code together. The mindset that set me free from this was: most of the time the code is really not right yet, but discovering it and fixing it can be another commit, no problem at all.
Taking a feature might be overwhelming sometimes. For me, what works is to approach it like it was a staircase that I can’t see the top: I focus only on the next stair (what it is, what it takes), work on that, and commit. And if I later find that something was not so well thought before and need some tweaks, no problem: fixing is just one more stair.
And it’s good to see what you couldn’t foresee in the commit history. Somehow, it improves your capacity to foresee similar situations in new occasions.
Anyways, really appreciate the article; your ideas were very well put. Loved your perspective and clarity about things we know we should do but don’t. Thanks for sharing!
I started doing this about two years ago while working at a smaller marketing agency and was doing a lot of my code late at night. I found that having the "safety net" made me more willing to try new things as I could revert mistakes pretty quickly.
I'm the same way. But I still ship atomic commits. Once I get 80-90% of my feature done and know where I'm going with it, I commit it all in one big WIP commit in a branch, then I go back, start from
main, and pull out pieces that I know are atomic commits into a new branch and ship those.
Sounds like you need to learn line commits. git gui will let you explicit choose the lines to stage. This is good as you generally need some changes from different files.
Often I'll do more of the feature, and then do line commits to tidy it up and make review easier, but sometimes you can't do that. Sometimes there are stepping-stone changes on one of those lines required.
I mean you could just do it that way, but each commit before it might not make sense.
An example being a new method, then realizing you want to rename other methods for better context or uniformity (whatever the reason). Those would be two atomic commits. But if you did it at the end through line commits, one of them will be a bit off and perhaps harder to review.
Well refactorings can be hard to separate with this method. Though I do think the method rename goes with the changes if the changes create context to the change.
Are you cherry picking?
No, because my commits were a mess in the WIP branch. Instead, I'm checking out files from my WIP branch, maybe even modifying them so they are independently commitable and shippable, and then commiting those atomically.
It sounds like a lot of work, but I just view it as part of the job. I, personally, can't make small independent changes until I roughly know what I'm trying to do.
I do something similar. When I know the steps I need to take, I write the commits like described in the article. However, when the task and required algorithms are poorly understood, it's more like a research project than development project and in that case my project history is a mix of atomic commits and snapshots. Even then I create atomic commits for all the details that are obvious enough that I know will be logical steps in the final version history.
And since you can reorder atomic commits, I usually end up joining and splitting the snapshot commits using interactive rebase to tease out atomic commits from snapshots. More often than not, the snapshots boundaries do not match any of the final commits and that simply demonstrates how poorly workspace snapshots match with logical atomic changes.
Very interesting addition, thanks
On that note, I can't recommend Sublime Merge highly enough as a commit-wrangling tool. I don't even use Sublime Text, but I can't live without their (completely standalone, unrelated) Git tool.
It has the best and most usable 3-way conflict resolution tool I've seen so far, plus some great features for massaging sets of branch commits into shape for pushing / submitting for review.
One of my favorites: As long as a branch's history is linear, you can select any random set of commits — interspersed by as many or as few other commits as you need — and squash them in various ways.
If you right-click and select "Squash selected commits", it'll do a standard squash, combining them into a single commit with their commit messages concatenated, placed where the oldest commit was in the history.
If you select "Squash selected commits, ignoring new messages (fixup)", OTOH, it'll incorporate the subsequent commits into the first without changing its commit message. Great for folding in that typo-correction you didn't catch until after you'd made three other interim commits (one of the hazards of developing with atomic commits).
Sublime Merge also has the ability, on a branch with no uncommitted changes, to "Edit commit contents" for any commit directly reachable from the HEAD (no interim merges). It'll undo the commit, leave the changes staged, and initiate an interactive rebase. You can unstage whatever sections ("hunks") or even individual lines you need, if you decide they belong in other/separate commits, or even discard some changes outright.
Then when you commit the updated changes, it completes the rebase to re-apply any following commits. If there are any conflicts, you resolve them along the way. (And like I said, it's the best tool I know for that part of the job.) When it's done, you end up with a fully-rewritten history, and possibly some unstaged changes if you removed anything from the edited commit but didn't discard it.
All of these are things it's possible to do with other tools, or even on the command line, sure. But a commit history is an inherently spatial concept, and I find working with it graphically is easier than just visualizing it. Sublime Merge hits the right balance between tools bolted onto code editors (which suffer from not being single-purpose enough) and the
gitcommand (which isn't high-level enough).
Before I started using it, like Adam I'd typically create a new branch, then start pulling over commits, making changes along the way. I still do that if it's a truly squirrely set of changes, but a lot of the time now I can just clean things up in-place, right on the original branch.
great experience shared Ramon.
i got a takeaway.
oh thats great
I feel like this is the hardest part of atomic commits. I commit very frequently, on almost every change however I do not tend to hold back if my test suites are not green. Remember to run tests before every commit is something I am going to try and take away from this article.
However, if you have to make +500/-500 changes to satisfy all of your tests before your first commit, which happens with very large codebases, the green machine is going to make atomic commits hard (in my opinion)
Git hooks can really help with this. I have many projects set up so they won't even let me commit if my tests don't run through first, and with tests that take longer, you can just postpone them to run before pushing. Of course, server-side automated CI is another good way of handling this, but I find that it's easier to ignore an email you get 5 minutes after you've already moved on to the next topic.
Good points thank you!
I do atomic commits first with all the changes I think are needed and then run the test for all the commits after the fact. I know that Git allows me to edit any commit after the fact (interactive rebase + edit) so I know I can fix testcase failures later before doing the pull request (or whatever workflow you have).
git commit --fixup
Git rebase is great way to keep commits going while still building commits that build and pass tests.
Great idea thanks!
One of the biggest advantages of atomic commits, imho, is that you can easily get a sort of technical change log of your project by just looking at
git log --oneline, which is specially useful when you want to write an actual change-log or decide which version number to increase for the next release.
Hello Samuel FAURE,
thank you for your article.
It is easy to undertsand and a good read, in my opinion.
Intiuitively I agree with everything what you wrote and I believe it is worth trying :).
"There is always a good reason to not do things right. And somehow we still end up surprised when we're doing things wrong."
I suppose that's what happens when we're in a workflow and don't think about working in a structured way, but more by feel.
Another reason could be that we don't understand or misunderstand the concept, so it's easier to skip the blanks until it's time to do it, and then we realize that "Damn, this doesn`t work as I hoped!".
This line really resonates. I recently read the chapter on Information Hiding in John Ousterhout's "A Philosophy of Software Design" and in a nutshell its all about simplifying complexity by hiding information in "deep" modules.
Sounds like good advice.
Could you elaborate on (2) please? What would be an example of a 'dirty' git history? Do you mean a history filled with commits where the message is something like 'stuff'?
For (3), do you create a PR after each commit, or after each feature (which could consist of many commits)?
Until recently I would add all changes post PR review to a commit "Post review fixes" instead of editing/rebasinf previous commits. This is a good example of a bad practice.
One PR per feature is good, preferably not a huge feature so the review can stay reasonable, huge features can be broken down onto smaller features.
Depending how reviews go, I'd recommend fixup commits post review. This way additional re-review can see your new changes. Before merge you do a rebase.
Right - so if I understand correctly, something like one for 'fixing typo', another for 'deleting extra lines'.
The harder and, at least for internal repositories, behavior that I push for is atomic small pull requests. I don't know why, but Git users seem to love pull requests with double digit commits rather than using
git commit --amend. I'm less interested in the requestor's thought process than I am in a clean linear commit history.
Small commits really started to pay off once I had to
git bisectto debug some strange behavior.
The main concern against atomic commits I heard often was: "But then you have so many commits in the history".
Honestly, I don't mind it. I'd rather have more commits than a hard to debug git history.
I would suggest that "cutting complex tasks down into smaller, manageable, tiny steps" could also be a commit.
Write a TODO comment block (where you detail the steps needed to complete the task). You can (even days or weeks later) go back and check what steps are done or to see if you forgot anything.
(You can use a separate [issue tracker] system to do this, which might be necessary if changes are needed outside of the code. It depends on a lot of things where it should be done, but it should be done somewhere. Doing it in code has the least overhead, the least excuses not to do it, and it is tightly coupled with the code so it is easy to check what was the plan/motivation behind a string of changes... even years later when the issue tracker might have been replaced.)
Very interesting take
Thanks so much for sharing Samuel, this is really succinct and well-put!
I really resonate with some of these comments you've made:
Adopting an atomic commit strategy was a huge step forward for slowing down and identifying why I was making certain design choices, properly digesting the consequences of those changes, and then in the future being able to understand when/why changes were made - really is a great long term investment especially when you're working with team(s) of people on longterm projects
I am absolutely guilty of this 😅 TDD has been on my agenda for a while now to properly take the plunge and integrate into my personal workflow from the beginning instead of as an afterthought that I never seem to get around to - I actually just started easing into it earlier this week and hopefully by the end of this year it'll be second-nature
Thanks again for sharing! I will absolutely be sharing this with some other folks I know, keep up the great content :D
Agree with this and how I have always worked with my branches, it provides me a better record of all the changes (in case I go off on a tangent) so there is a comprehensive commit of everything done. Also like the quick revert if you go too far down a path of destruction.
Great advice and consistent with the 'commit frequently' mantra. When we train ourselves to do this we will always have a range of prior commits to recover should we need it. Also I recommend the visual studio code gitlens plug-in. It's fantastic...
Can you further break down what an Atomic commit is ( in more detail…🙏) and a step by step of how-to-do or how you would approach it ?
I will definitely write a follow-up to this short article. Stay tuned :)
Thank you !
Atomic commits also allow bug fixes to be easily reviewed if only a single bug fix is committed at a time. Instead of having to check multiple potentially unrelated files the reviewer must only check files and changes that directly impact the bug being fixed.
regards: latest capcut mod apk
To do that we might need to have a plan in mind about what are we going to do as a step.
No you don't. Exploration is free. Go and explore, make a bunch of commits. But when you've found your way to your goal (if you goal in code was unknowable at the outset, which it often is), go back, cut a new branch from main, and start committing the work that you did, atomically.
Anybody can build a feature, but it takes significantly more skill to be able to ship it.
Forcing yourself to have a plan is half the benefit:)