- 1. Getting geeky with Git #1. Remotes and upstream branches
- 2. Getting geeky with Git #2. Building blocks of a commit
- 3. Getting geeky with Git #3. The branch is a reference
- 4. Getting geeky with Git #4. Fast-forward merge and merge strategies
- 5. Getting geeky with Git #5. Improving merge workflow with rebase
- 6. Getting geeky with Git #6. Interactive Rebase
- 7. Getting geeky with Git #7. Cherry Pick with Reflog
- 8. Getting geeky with Git #8. Improving our debugging flow with Bisect and Worktree
- 9. Getting geeky with Git #9. Understanding the revert feature
- 10. Getting geeky with Git #10. The overview of Git hooks with Husky
- 11. Getting geeky with Git #11. Keeping our Git history clean with fixup commits
In this series, we’ve put a big emphasis on keeping our Git history clean. A big part of it was using features such as rebase. In this article, we go further and learn about fixup commits. With them, we can easily modify changes we’ve introduced in a single commit in our history.
Let’s create a brand new repository to visualize better a situation in which the fixup commits can come in handy.
1 2 3 4 5 6 7 8 9 10 11 |
git init touch index.js git add ./index.js git commit -m "Initial commit" echo "console.log('Hell world');" > ./index.js git add ./index.js git commit -m "Added hello world" git log |
commit 675383fcbe5af72b38846e9d4b12d84d78bacee8 (HEAD -> master)
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 31 15:10:26 2021 +0200Added hello world
commit 9a45ed3c7d689ce6db85597b0a5eaf37f3064d07
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 31 15:09:56 2021 +0200Initial commit
Above, we’ve created a commit that added console.log('Hell world'); to the index.js file. Unfortunately, we’ve made a typo. Let’s fix it!
Fixing an error in a commit
One way of fixing the above issue would be to use interactive rebasing.
If you would like to know more about interactive rebasing, check out Getting geeky with Git #6. Interactive Rebase
1 |
git rebase -i HEAD~1 |
When we’ve started the process of rebasing, we now need to modify our file.
1 2 |
echo "console.log('Hello world');" > ./index.js git add ./index.js |
Once we’ve fixed our error, we need to amend the commit.
1 2 |
git commit --amend git rebase --continue |
Successfully rebased and updated refs/heads/master.
Introducing fixup commits
Unfortunately, all of the above can be quite a chore. Because of that, we might be tempted to skip it and create a brand new commit that fixes our mistake. However, with fixup commits, we can do that while still maintaining a clear history!
First, let’s implement the changes we need.
1 2 |
echo "console.log('Hello world');" > ./index.js git add ./index.js |
To create a fixup commit, we need to obtain the hash of the commit we want to modify.
1 |
git log |
commit 675383fcbe5af72b38846e9d4b12d84d78bacee8 (HEAD -> master, origin/master)
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 31 15:10:26 2021 +0200Added hello world
commit 9a45ed3c7d689ce6db85597b0a5eaf37f3064d07
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 31 15:09:56 2021 +0200Initial commit
We now need to create a commit with the --fixup flag.
1 |
git commit --fixup 675383f |
Git only needs a part of the commit’s hash to identify it. If you want to know more, check out Getting geeky with Git #2. Building blocks of a commit
Doing the above creates a brand new commit with the fixup! prefix in the message and adds it to our history.
1 |
git log |
commit 1652fdd237589ca31ed7416da1c45b4158c22a8f (HEAD -> master)
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 31 15:55:21 2021 +0200fixup! Added hello world
commit 675383fcbe5af72b38846e9d4b12d84d78bacee8 (origin/master)
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 31 15:10:26 2021 +0200Added hello world
commit 9a45ed3c7d689ce6db85597b0a5eaf37f3064d07
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 31 15:09:56 2021 +0200Initial commit
Squashing the commits
The last step in cleanup up our Git history is squashing the fix with the commit it improves. To do that, we need to perform an interactive rebase with the --autosquash flag.
1 |
git rebase -i --autosquash HEAD~2 |
We can use git config rebase.autosquash true to make this a default behavior.
Doing the above squashes the original commit with the fix giving us a clean history.
The above is a simple example. The golden rule of rebasing is to avoid doing it on branches used by other developers, because it involves overwriting history and would cause issues for our teammates. Doing it on a master branch would not be a good idea in a real project.
Things to watch out for when creating fixup commits
Instead of using the git commit --fixup command, we can create a fixup commit manually.
1 |
git commit -m "fixup! Added hello world" |
Doing the above will also cause Git to recognize this as a fixup commit when rebasing with the --autosquash flag. This shows that Git uses the commit messages to figure out what commit the fixup belongs to. This can cause some issues. Let’s create a second commit with the same message:
1 2 3 4 5 |
echo "console.log('Hell world 2')" > hello.js git add ./hello.js git commit -m "Added hello world" git log |
commit 3d10af1b5affeb5b0ad4e1d24d302ae53ee93f59 (HEAD -> master, origin/master)
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 31 17:33:20 2021 +0200Added hello world
commit 675383fcbe5af72b38846e9d4b12d84d78bacee8
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 31 15:10:26 2021 +0200Added hello world
commit 9a45ed3c7d689ce6db85597b0a5eaf37f3064d07
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 31 15:09:56 2021 +0200Initial commit
Above, we can see that we have two commits with the same commit message. Now, let’s create a fixup for the latest one:
1 2 3 |
echo "console.log('Hello world 2')" > hello.js git add ./hello.js git commit --fixup 3d10af1 |
Doing the above results in creating a new fixup commit:
commit fe507faafd83989cf0d85e305d6001725f4db926 (HEAD -> master)
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 31 17:39:23 2021 +0200fixup! Added hello world
commit 3d10af1b5affeb5b0ad4e1d24d302ae53ee93f59 (origin/master)
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 31 17:33:20 2021 +0200Added hello world
commit 675383fcbe5af72b38846e9d4b12d84d78bacee8
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 31 15:10:26 2021 +0200Added hello world
commit 9a45ed3c7d689ce6db85597b0a5eaf37f3064d07
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 31 15:09:56 2021 +0200Initial commit
Because the fixup matches more than one commit, we need to proceed with caution when rebasing.
1 |
git rebase -i --autosquash HEAD~2 |
Since above, we’ve used HEAD~2, Git takes only the latest two commits into account when rebasing. Therefore, it worked out without issues.
The order of the commits when rebasing
We might encounter a problem if we rebase more commits and more than one matches the fixup commit.
1 |
git rebase -i --autosquash HEAD~3 |
Fixup works similarly to squashing. It melds the fixup commit into the previous commit. Above, Git attempts to use the fixup commit for the wrong commit and gives us a conflict. To deal with it, we need to move the line with the fixup commit down.
Summary
In this article, we’ve gone through the feature of fixup commits. They can be handy when we want to alter a commit straightforwardly. The easier this process is, the less often we will take a shortcut and create an additional commit messing our history. Since the fixup and autosquash features rely on commit messages, we’ve also dealt with some issues we might encounter.
Why do all this when you can squash merge (branch-first, never rebase)?