Squashing commits is the process of merging changes from one or more commits into a single commit instead. This allows us to rewrite the history of our git repository retroactively, allowing us to appear more organized than we actually are. It also has the benefit that if you squash changes on sensible intervals, it can make it easier to roll back features / changes using git revert
.
In a scenario where you’re not specifically looking to clean up your recent history before you continue working, but just want to merge your work into another branch e.g. main, you can save yourself some time and do the following instead:
git checkout main
git fetch && git pull
git merge --squash feature-branch
git commit -m "feat: Added Domain Engine to project"
This will squash all commits from the branch named “feature-branch” into a single commit on the main branch.
You’ve been working on your feature on the branch named ‘feature-branch’ for some time now, committing as you go along. Our history looks like this:
mha@hakkeboksen:~/dev/git-example$ git log
commit 88be53edac2b01af33b3136e3133064fe9b46b87 (HEAD -> feature-branch)
Author: Morten Hauge <morten.hauge97@gmail.com>
Date: Sat Nov 4 18:22:21 2023 +0100
test: Wrote tests for domain engine
commit e3a2fd05d30f5efb6cbb9b19bfd246ebfcc77cff
Author: Morten Hauge <morten.hauge97@gmail.com>
Date: Sat Nov 4 18:22:14 2023 +0100
chore: Done with domain engine, missing tests
commit 58ef93a86d2d441ec5aae32b33e1ef8e5a257d59
Author: Morten Hauge <morten.hauge97@gmail.com>
Date: Sat Nov 4 18:22:07 2023 +0100
chore: Rewriting domain engine...
commit 6396166205f8a4a4a8b46d68201805fc2611fe7e
Author: Morten Hauge <morten.hauge97@gmail.com>
Date: Sat Nov 4 18:21:56 2023 +0100
chore: WIP. Committing to save current state
commit 7d5401f5b7404db86ba7930d98af6dad42990294
Author: Morten Hauge <morten.hauge97@gmail.com>
Date: Sat Nov 4 18:21:41 2023 +0100
fix: Resolved an issue where users were unable to delete comments
commit 9a92dcfad0ca66f418d40b7920312e45fa0dd71f (main)
Author: Morten Hauge <morten.hauge97@gmail.com>
Date: Sat Nov 4 18:21:13 2023 +0100
chore: Initial commit
You’re finished with a large portion of the work for the feature, and want to squash all of your recent chagnes into a single commit, such as “feat: Added Domain Engine to project”, before you continue working on the next part of the feature in your current branch.
At this point our sources on the internet recommends that we make an offline backup of our entire project, since we are about to do an interactive rebase, and chances are high we are going to irreparably mess up our project. Instead of doing this I recommend the following:
Grab the hash of the last commit you want to appear in the history before your soon-to-exist squash commit. In our case this is 9a92dcfad0ca66f418d40b7920312e45fa0dd71f
with the text chore: Initial commit
since I want my squash-commit to appear directly after this.
Run git status
to quickly verify that we have no unstaged changes or unpushed commits:
mha@hakkeboksen:~/dev/git-example$ git status
On branch feature-branch
nothing to commit, working tree clean
Run git reset --soft 9a92dcfad0ca66f418d40b7920312e45fa0dd71f
Run git commit -m "feat: Added Domain Engine to project"
Note that it is also possible to use relative commits, such as HEAD~3, but I find that this is often more work than just grabbing the hash directly from git log
.
When you ran git reset --soft
you told git to point your HEAD at the commit you specified, without touching the index or your working tree. As a result, all the files that have been edited between HEAD and the specified commit are now automatically staged with the content they had before you issued the reset, and the commits between them have been “undone”. This allows us to create a new commit using standard git commit
syntax. Similarly to a rebase, since we have rewritten the commit history, we now need to perform a force push to have our changes reflected upstream.
Essentially, to git
everything appears as if you made all your changes without making any commits in between and now want to make a huge commit.
git push -f origin feature-branch
Let’s look at the commit history after running our commands:
mha@hakkeboksen:~/dev/git-example$ git log
commit 0b73c96db879796645c1d6e28566670fecfd23b0 (HEAD -> feature-branch)
Author: Morten Hauge <morten.hauge97@gmail.com>
Date: Sat Nov 4 18:37:57 2023 +0100
feat: Added Domain Engine to project
commit 9a92dcfad0ca66f418d40b7920312e45fa0dd71f (main)
Author: Morten Hauge <morten.hauge97@gmail.com>
Date: Sat Nov 4 18:21:13 2023 +0100
chore: Initial commit
Note that the trick above only works if you want to squash all commits between HEAD and the commit you specified, but I find that this is the case more often than not.