DEV Community

Cover image for How atomic Git commits dramatically increased my productivity - and will increase yours too

How atomic Git commits dramatically increased my productivity - and will increase yours too

Samuel-Zacharie FAURE on March 07, 2023

Atomic: of or forming a single irreducible unit or component in a larger system. Also available on my blog. Knowing VS Actually Knowing ...
Collapse
 
ramonkcom profile image
Ramon Kayo • Edited

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

Collapse
 
danbailey profile image
Dan Bailey

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.

Collapse
 
akmjenkins profile image
Adam • Edited

is that I was never sure if what I did was right before I saw the whole code together.

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.

Collapse
 
jessekphillips profile image
Jesse Phillips

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.

Thread Thread
 
nicklarsennz profile image
Nick

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.

Thread Thread
 
jessekphillips profile image
Jesse Phillips

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.

Collapse
 
cawoodm profile image
Marc

Are you cherry picking?

Thread Thread
 
akmjenkins profile image
Adam

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.

Thread Thread
 
mtrantalainen profile image
Mikko Rantalainen

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.

Thread Thread
 
samuelfaure profile image
Samuel-Zacharie FAURE

Very interesting addition, thanks

Thread Thread
 
ferdnyc profile image
Frank Dana

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.

  1. 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.

  2. 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).

Thread Thread
 
ferdnyc profile image
Frank Dana

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.

Collapse
 
jake0011 profile image
JAKE

great experience shared Ramon.

i got a takeaway.

Collapse
 
artarya profile image
artarya

oh thats great

Collapse
 
damiensedgwick profile image
Damien Sedgwick

Ideally, you also want your test suite to be in the green when you commit.

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)

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Remember to run tests before every commit is something I am going to try and take away from this article.

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.

Collapse
 
damiensedgwick profile image
Damien Sedgwick

Good points thank you!

Collapse
 
mtrantalainen profile image
Mikko Rantalainen

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).

Collapse
 
jessekphillips profile image
Jesse Phillips

git commit --fixup

Git rebase is great way to keep commits going while still building commits that build and pass tests.

Collapse
 
damiensedgwick profile image
Damien Sedgwick

Great idea thanks!

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

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.

Collapse
 
schultyy profile image
Jan Schulte

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.

Collapse
 
incrementis profile image
Akin C.

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!".

Collapse
 
ant_f_dev profile image
Anthony Fung

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)?

Collapse
 
samuelfaure profile image
Samuel-Zacharie FAURE

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.

Collapse
 
jessekphillips profile image
Jesse Phillips

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.

Collapse
 
ant_f_dev profile image
Anthony Fung

Right - so if I understand correctly, something like one for 'fixing typo', another for 'deleting extra lines'.

Collapse
 
syedashrafulla profile image
Syed Ashrafulla

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.

Collapse
 
janarth10 profile image
janarth10

After all, simplifying complexity is the very core of our job

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.

Collapse
 
jwp profile image
John Peters

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...

Collapse
 
chrisgreening profile image
Chris Greening

Thanks so much for sharing Samuel, this is really succinct and well-put!

I really resonate with some of these comments you've made:

"If you're like me, you might have a tendency while developing a feature, to just... do it"

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:

"So many people out there, just like I did before, know how TDD is great... yet still don't use it."

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

Collapse
 
szikra profile image
szikra

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.)

Collapse
 
samuelfaure profile image
Samuel-Zacharie FAURE

Very interesting take

Collapse
 
mfurmaniuk profile image
Michael

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.

Collapse
 
jeje profile image
Jérôme Reybert

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.

Collapse
 
ilumin profile image
Teerasak Vichadee

To do that we might need to have a plan in mind about what are we going to do as a step.

Collapse
 
akmjenkins profile image
Adam

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.

Collapse
 
samuelfaure profile image
Samuel-Zacharie FAURE

Forcing yourself to have a plan is half the benefit:)

Collapse
 
ota profile image
Olu Tobi Akinyemi

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 ?

Collapse
 
samuelfaure profile image
Samuel-Zacharie FAURE

I will definitely write a follow-up to this short article. Stay tuned :)

Collapse
 
ota profile image
Olu Tobi Akinyemi

Thank you !

Collapse
 
samuelfaure profile image
Samuel-Zacharie FAURE

As usual, comments are welcome.

Collapse
 
tinaheiligers profile image
Christiane (Tina) Heiligers

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.

Collapse
 
samuelfaure profile image
Samuel-Zacharie FAURE

This is the way

Collapse
 
tythos profile image
Brian Kirkpatrick

Benefits for PR/MR reviews alone is worth it.

Collapse
 
macdanson profile image
Nahabwe Danson

I think I should actively start this approach. I have heard of it over and over but hestant to start

Collapse
 
jatin510 profile image
Jagdish Parihar

hey !!
Can you please show some example as well.
A sample open source project, will be a huge help to understand the concept.
Thanks !!

Collapse
 
samuelfaure profile image
Samuel-Zacharie FAURE

Stay tuned for part 2 :)

Collapse
 
jake0011 profile image
JAKE

thanks Sam, this is a very informative piece.

Collapse
 
christian_go3 profile image
Christian GO

Great advice, thank you!

Collapse
 
justjenfelice profile image
Jennifer Hageman-Culp

How do you handle a rebase?

Collapse
 
samuelfaure profile image
Samuel-Zacharie FAURE

Probably going to need a part 2 for this question. Stay tuned!

Collapse
 
allbertuu profile image
Alberto Albuquerque

Nice post! It would be nicer if you've brought examples to how write these commits. But after all, you made it!

Collapse
 
nabbe profile image
Nick Abbene

Also understated is how easier it is to change the order of things if needed (breaking up a big MR, or reordering stacked MRs)

Collapse
 
ellis_85 profile image
ellis-

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.

Collapse
 
samuelfaure profile image
Samuel-Zacharie FAURE

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

Collapse
 
ferdnyc profile image
Frank Dana

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.

Thread Thread
 
samuelfaure profile image
Samuel-Zacharie FAURE

Yeah you'd definitely need to rebase -i those 500 commits, that seems unnecessary

Collapse
 
cawoodm profile image
Marc

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.

Collapse
 
jwp profile image
John Peters

I do this too and add the date and comments telling the state.

Collapse
 
mtrantalainen profile image
Mikko Rantalainen

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.

Collapse
 
samuelfaure profile image
Samuel-Zacharie FAURE

However, even if the commit can be described with just the title, you should still write longer commit messages

I should have sais this clearly in the article, you're right!

Collapse
 
bretbernhoft profile image
Bret Bernhoft

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?

Collapse
 
ryancurrah profile image
Ryan Currah • Edited

Squashing commits on merge could accomplish this.

Collapse
 
samuelfaure profile image
Samuel-Zacharie FAURE

Squashing commits on merge is the devil, rebasing without any squash on merge is the way to go