Git Reset: Using git reset to squash your first commit
by Henry Brown
If you have ever (prematurely) pushed your first commit to a remote git repository and then realised that you would like to have that commit back before pushing it then this one is for you.
If you have attempted to use interactive rebase to fix this situation you will have discovered that it cannot help in
this particular situation. However, that does not mean all is lost. Using the git reset
command, it is possible to
replace that first commit with a different commit.
Let us look at the steps required to perform this feat.
Let’s start by cloning a remote repository:
git clone https://henry-vine@bitbucket.org/henry-vine/rebase-demo.git .
Now we are effectively in the same situation described above. We have a remote repository with a single commit and a local repository that is up to date with that single repository.
To simulate a change, that we have forgot to add to our first commit, let us add a single line to one of the files and commit that file:
vi first.txt
# edit file
# stage changes
git add .
# commit changes locally
git commit -m "second commit"
We can now see that our local repository is ahead of the remote repository:
# local repo is ahead of remote
git status
Now, if we wanted to add this latest edit to our first commit it might come to our mind that we could use interactive rebase to squash these 2 commits:
git rebase -i HEAD~1
Unfortunately, as previously mentioned, this will not work as there is only a single commit in the file.
However, we can use the reset
command to remedy this situation.
First we use reset
to move our HEAD
pointer to the previous commit:
git reset --soft HEAD~1
Now if we run the status
command we will see that the state of our local repository is the same as it was before we performed
our second commit.
git status
less first.txt
We can further confirm this by looking at the log:
# now log shows only first commit
git log --oneline
Since we have effectively time-traveled to a state before we performed our second commit, we can now perform an amend commit instead:
git commit --amend
If we would like, Git even offers us an opportunity to change our commit message to more accurately reflect the (amended) first commit.
If we look at our first commit in our local repository now, we will notice that the id associate with our first (and only) commit has changed:
git log --oneline
This is because commits in Git are immutable and as such a new commit had to be created that simply replaced the previous first commit. However, running a status check on our local repository also reveals a further problem:
git status
That is that we have now diverged our local and remote repository. In other words, Git see a commit id on the remote repository that is not present on our local repository (as well as a commit id in our local repository that is not present on the remote).
As such, Git has not idea how it should automatically reconcile the state of our local repository with that of the remote.
Thus, to get our changes from our local repository into our remote, we will have to rewrite the history of our remote.
The common way to do this is using a the force
option when perform the push.
The danger with this command, is that if the remote repository is a shared repository where someone else may have pushed changes to,
we would be overwriting their changes (and effectively removing them from the remote history).
As such, it is always best practice using the forece-with-lease
option instead. This option will rewrite the history only if no other commits have been made.
Thus, the final step to this process is to run:
git push --force-with-lease
Now, looking at the remote branch, you should see that the one only commit is the amended commit with the same id as on your local repository.
If you prefer, watching this all play out in a video format, have a look my video on YouTube: https://youtu.be/CGrHpeQpzUE
You can also see the repository on Github: https://github.com/hgbrown/git-rebase-reset-demo
tags: git - reset - rebase - vcs