Atomic: of or forming a single irreducible unit or component in a larger system.
Also available on my blog.
Knowing VS Actually Knowing
...
For further actions, you may consider blocking this person and/or reporting abuse
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!
+1
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
git
command (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.Small commits really started to pay off once I had to
git bisect
to 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.
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!".
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)?
Hello,
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'.
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.
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.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
and also:
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...
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
It's really super hard without good tools. We need good tools. We could have had good tools, but the makers of GitHub/Azure DevOps/whatever don't know that this is good.
I also completely agree with you about knowing vs. really knowing. It's impossible to explain to people why this is such a productivity booster (if your tools don't get in the way and hopefully help)
Excellent article, it highlights with simple words something difficult to share with colleagues.
I tend to stick to this motto since years, with more or less success. I can see two things that helped me recently.
First one is, finding a way to stay focus on what is the primary goal of the task. For development tasks such as adding a new feature, or worse, refactoring, you may go through a lot of code, spotting a lot potential fix/enhancement. It is really hard to not fix them at the moment, usually because you fear to forget it instanly you'll jump to the next function. But what seems easy and contained in a single function at the moment may end with patches all around the project. And you end with two different feature in your stage area, maybe hard to split.
The answer for that is: add an issue, with minimum context to work on it later (you, or another developper). Then, the issue will be addressed in a clean codebase, at the right moment, which is a good start for a new atomic commit.
The second tip is to put less pressure on the atomic commit. If you use merge requests, on your project, setup merge request to use semi linear history (for example in gitlab). Even if each individual commit is not strictly atomic, your git log will naturally be cut into merge requests atomic sections.
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 !
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:)
As usual, comments are welcome.
Benefits for PR/MR reviews alone is worth it.
hey !!
Can you please show some example as well.
A sample open source project, will be a huge help to understand the concept.
Thanks !!
Stay tuned for part 2 :)
Great advice, thank you!
thanks Sam, this is a very informative piece.
I think I should actively start this approach. I have heard of it over and over but hestant to start
I find that a combo of committing often with descriptive commit messages really helps during WIP.
Before submitting the work though, I do a clean-up and rebase to give reviewers an easier time.
This is the way
Also understated is how easier it is to change the order of things if needed (breaking up a big MR, or reordering stacked MRs)
Nice post! It would be nicer if you've brought examples to how write these commits. But after all, you made it!
How do you handle a rebase?
Probably going to need a part 2 for this question. Stay tuned!
Very interesting. I already used a similar approach in my commits and division of tasks, now I discovered that it has a name.
I absolutely love it. I am gona start doing this from now. Will post the update after 100days.
Everyone has their workflow, good or bad. But saying that the intermediate commit history of your work is of any importance for reviewing, sounds off.
I don't care about how you screwed up, and mid-feature reconciled with the code gods.
I'm reviewing your finished feature, not your ability to atomize your work.
Squash merge 4-lyfe.
I'm going to be honest, this kinda sounds to me like "I don't care about code quality, only the result"
Not really my philosophy at all; lots of problem downstream from there imho
No, I think ellis has a point.
Submitting a PR containing one commit that changes 1500 lines of code in 10 files is just rude, and borderline unreviewable.
But submitting a PR containing 500 commits, each making a 1-5 line change (and some of them stealth-reverting — or explicitly-reverting — earlier commits that made bad changes) is no less rude, and just as much a chore to review.
A branch containing a bunch of "Fixed typo" commits is the opposite of a clean git history, and ideally by the time it's pushed for review, the history has been rewritten cleanly so there's none of that left.
Yeah you'd definitely need to rebase -i those 500 commits, that seems unnecessary
I commit and push branches at the end of the day regardless of whether the code runs or not so that the code is backed up in case my machine dies. Since my work ends up in a PR merged via squash commits we have a similar effect to what you are describing although a PR may contain a long description of changes.
I do this too and add the date and comments telling the state.
Good tips. However, even if the commit can be described with just the title, you should still write longer commit messages. You should explain why the commit should be included in the codebase.
This will help in the future when some other developer tries to understand the code. You can then use tools such as git blame to get line level documentation of every single line in the system about why that line of code is needed.
When future developer them comes by and inspects the why for the code he or she needs to modify, it will be obvious what needs to be considered while modifying that code. It often turns out that the old assumptions no longer apply and the code needs to be changed radically. However, without the documentation why that line was needed, understanding that will be much harder.
And commit messages are better for this than comments because you can have a long comment for a single line change for complex situations. You could write all the same data in the source code comments but then you have to maintain it during merges to keep it synchronized with the code. When you store it in git commits, it will always match the correct line, plus tools such as git gui blame allow you to easily read the messages for the previous versions of the same line, too.
I should have sais this clearly in the article, you're right!
This is great. I will reconsider my Push frequency, based on the suggestions in this article. Have you run into any resistance with this style of workflow?
Squashing commits on merge could accomplish this.
Squashing commits on merge is the devil, rebasing without any squash on merge is the way to go