Small, atomic commits makes it easier for code reviews, browsing the history and reverting changes. Life happens and commits can touch more lines and files than I want but the changes committed should be distinct. "Don't mix your apples with your toaster."
TL;DR: Use
git add -i
orgit add -p
. Stage the files you know are related. Break other mangled changes in files into smaller chunks (known as hunks) using patch mode. Stage. Commit. Do it again. Be careful.
In this post I am going to show you how to craft your changes into small atomic commits. This means that you are going to break up your changes into separate pieces of work and commit them.
It was brought to my attention in Reddit by WetDynamics that "atomic commits sound nice in theory but in practice you end up with 100 commits of "extracted foo into a method" or "refactored bar to make it more readable". Does it really make your git history easier to grok than a single commit focused on a feature?" So I wrote about it.
Know what you are doing. If you make a mistake by omitting a file or leaving out a chunk of a file you will have a broken commit.
Doing this is much easier when you keep your changes as small and simple as possible. Although you can scale up, the more complex your changes are, the more your brain has to work and the larger the chances are of you making a mistake.
There are multiple ways to breakdown changes into smaller commits:
The not so cool way
The Git way
git diff layout.scss
will give you the diff for that file. You
will notice some seriously stylish changes that I have made.
Quickly check the status of the file in git status
and see that
it is already staged. This file must not be committed in its
entirety so the file must not be staged.
➜ ahoy git:master ✗ 9cc08bf ➜ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: data/ship.json -> data/ships.json
modified: layout.scss
modified: src/pirate.clj
modified: components/parrot.jsx
This is the meat of what I want to share with you. I am going to
show you how to stage files and parts of files to craft your
commits.
Get started with git add -i
or git add --interactive
See a
status screen which is like git status
on steroids.
To the left you have staged changes. If the file hasn't been staged yet it will display unchanged.
To your right you have unstaged changes. If the file is fully staged then it will display nothing.
➜ ahoy git:master ✗ 9cc08bf ➜ git add -i
staged unstaged path
1: +0/-12 nothing data/ship.json
2: +16/-0 nothing data/ships.json
3: +8/-2 nothing layout.scss
4: +4/-4 nothing src/pirate.clj
5: unchanged +2/-2 components/parrot.jsx
You are presented with a bunch of options that will aid you on
your journey. Let's take a look at them:
What now>
is the base prompt patiently awaiting your commands.
Type in any of the numbers or letters below:
1
or s
to see the steroid status screen again
2
or u
lets you stage files. When you select it, it will
show files that have not been staged. This is equivalent to git add <file>
What Now>
prompt. If there
are files to be staged then you will enter the Update>>
prompt.ENTER
. The files are shown again. This time you will notice asterisks
next to the files you have selected. They are the one's that will be staged.ENTER
to stage the files.ENTER
3
or r
will do the opposite of update. It will unstage (or revert)
your changed file. This is equivalent to git rm --cached <file>
The process
is exactly the same as above except you will be in the Revert>>
prompt.
4
or a
will add any untracked files. Again, the process is the same but
you will be in the Add untracked>>
prompt. This initiates a git add <file>
5
or p
will allow us to selectively choose parts of a file (known as hunks)
and stage just that part. You will be in the Patch update>>
prompt. Same story
as before. I'll get to this in a bit.
6
or d
will diff any staged file. Again it is the same above but now you
will be in the Review diff>>
prompt and you will be seeing diffs for staged
files as you would in git diff --cached <file>
You are presented with hunks. The layout.scss
file below only has one hunk.
Uh, not ideal because I want to break it up into atomic commits.
Patch update>>
diff --git a/layout.scss b/layout.scss
index fcc3ea4..8bdf8b6 100644
--- a/layout.scss
+++ b/layout.scss
@@ -1,9 +1,15 @@
+h1 {
+ font-size: 3em;
+}
.parrot {
background-color: blue;
+ border: solid 1px green;
}
.ship {
- background-color: green;
+ background-color: brown;
+ font-size: 2.5em;
}
.pirate {
- background-color: red;
+ background-color: black;
+ border: solid 1px green
}
Stage this hunk [y,n,q,a,d,s,e,?]?
Ahoy matey, there be options:
y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk or any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk or any of the later hunks in the file
s - split the current hunk into smaller hunks <-----Booya!
e - manually edit the current hunk
? - print help
Press s
to split that hunk into baby hunks. Now there are more options
Stage this hunk [y,n,q,a,d,K,g,/,e,?]?
...
g - select a hunk to go to
/ - search for a hunk matching the given regex
K - leave this hunk undecided, see previous hunk
...
Git was able to split that file into 4 hunks which you can choose to stage or not. Stage the ones that are relevant to your commit and leave the rest for now. You won't lose those changes.
Hunk #1
@@ -1,2 +1,5 @@
+h1 {
+ font-size: 3em;
+}
.parrot {
background-color: blue;
Hunk #2
@@ -1,4 +4,5 @@
.parrot {
background-color: blue;
+ border: solid 1px green;
}
.ship {
Hunk #3
@@ -3,5 +7,6 @@
}
.ship {
- background-color: green;
+ background-color: brown;
+ font-size: 2.5em;
}
.pirate {
Hunk #4
@@ -6,4 +11,5 @@
}
.pirate {
- background-color: red;
+ background-color: black;
+ border: solid 1px green
}
When a hunk cannot be split you will see the following in which can you can edit the hunk.
Sorry, cannot split this hunk
@@ -9,4 +9,8 @@
"pirate": "Sailor Tom the Plank",
"parrot": "Major Idea"
}
+ {
+ "ship": "Mother Octopus",
+ "pirate": "Pi",
+ "parrot": "Log"
]
This @@ -1,9 +1,11 @@ line specifies the boundaries of the hunk to change. The -1,9 means the original portion of code started at line 1 in the code below and went for 9 more lines. The +1,11 means that we want to replace the original portion with the following code starting at line 1 and spanning the next 11 lines. ~ http://blog.jimbaca.com
To wrap it up, select the related files and create the related hunks. Stage them. Quit and commit. Do it again for the next change.
You can also use git patch directly. git add -p
or
git add --patch
will take you into patch mode.
git reset --patch
git checkout --patch
git stash save --patch