iTranslated by AI
Git Tips: How to Undo a Committed Mistake
Introduction
In this article, I would like to introduce some Tips for operating Git.
Specifically, I will cover how to deal with situations where you have made an "incorrect commit."
While using revert for undoing changes is commonly introduced for such cases, this time I will also introduce methods using reset.
Common Steps
First, I will describe the process common to both revert and reset.
Since the fundamental goal is the same—to make it as if the "incorrect commit" never happened—you first need to identify "which commit to nullify" from the commit history.
Referencing the Commit Log
Use the git log command to view the commit history.
git log
Past commits will be displayed in order, as shown below.
commit xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Author: hogehoge hogehoge@example.com
Date: Tue Dec 22 17:03:53 2020 +0900
Modified Fuga.txt
commit yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
Author: fugafuga fugafuga@example.com
Date: Tue Dec 22 14:10:20 2020 +0900
Added Fuga.txt
commit zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
Author: punipuni punipuni@example.com
Date: Tue Dec 22 12:00:00 2020 +0900
Initial commit
The important part here is the commit hash.
In the example above, strings like xxxxxx... or yyyyyy... correspond to this.
When using revert
revert performs an "undo" on the target commit. Since the fact that it was undone remains in the commit log, you can refer to it later, like "a commit that undid commit xxx."
To do this, use git revert [target commit hash]. Therefore, if you want to undo the commit yyyyyy...:
git revert yyyyyy
To reiterate, what this command generates is a "new commit that undid the commit yyyyy...". So, you naturally need to push to reflect this on the remote.
How to specify a commit
There are various ways to specify the target commit for undoing in revert besides using the hash. *Many of the methods below are effective for specifying commits in general, not just for revert.
Specifying the previous commit
To specify the immediate previous commit, you can use HEAD^.
git revert HEAD^
Specifying the N-th previous commit
HEAD~n represents the N-th previous commit. To specify the commit from two versions ago, do the following:
git revert HEAD~2
Specifying a range
You can specify a range of commits using ... For example, you can use it like this:
git revert zzzzzz..xxxxxxx
git revert HEAD~3..HEAD^
When using reset
reset literally resets to a specified commit (or that range).
*Strictly speaking, I think it's more accurate to describe it as resetting the state of the aforementioned HEAD.
Basic usage is the same as revert.
git reset yyyyyy
Options
reset has options to specify to what level the reset should be performed.
Specifically, these are --mixed, --soft, and --hard.
To introduce the differences between each, let's assume hoge.txt was newly created, added, and committed through the following three steps.
- ① Create hoge.txt
touch hoge.txt
- ② add hoge.txt
git add hoge.txt
- ③ commit hoge.txt
git commit -m 'Add hoge.txt'
mixed
This is the default if no option is specified.
The characteristic is "deletes the commit and the index, but keeps the file changes."
In other words, in the example above, ② and ③ are reset, returning to the state where only ① was performed.
git reset --mixed HEAD^
soft
soft is a milder reset than mixed.
As a characteristic, it "only deletes the commit," so the state returns to after ② was performed.
git reset --soft HEAD^
hard
hard is a complete reset.
Therefore, it returns to the state where none of ①, ②, or ③ were performed.
Be careful, as file changes are also discarded.
git reset --hard HEAD^
If you want to undo a hard reset
You might accidentally double down on a mistake and find yourself saying, "Oh no, I reset --hard a correct commit..."
As mentioned, while reset --hard makes everything disappear, there is a way to restore it.
In fact, there is a command called git reflog, and inside it, the logs of your reset --hard are properly preserved.
git reflog
Since this allows you to find the hash of the state before you ran git reset --hard, you can run git reset specifying that hash.
git reset --hard [hash you want to return to]
Applying reset to remote
Since reset essentially resets commits, unlike revert, you cannot push a "commit representing the cancellation" to the remote.
If you have made an incorrect commit locally and have already pushed it to the remote, an additional step is required.
By adding the -f option during git push, you forcibly update the remote branch with the current contents of your local branch.
The following is a command example for forcing a push of the local branch content to master.
git push origin master -f
Since this forcibly updates the remote branch, if you do this after performing a reset, you can cancel the incorrect commit that was pushed to the remote.
*Note: This is a complete rewrite of history, so it is not recommended.
In case of a protected branch
The permissions and settings for development vary, so it cannot be said for certain, but you will likely see the following error at the stage of executing the above command:
You are not allowed to force push code to a protected branch on this project
This is because the target branch (master in the example) is protected. In this case, you need to change the settings.
In the case of GitLab, you can resolve this by opening the repository and going to [Settings] → [Repository] → [Protected Branches] and executing [Unprotect] for the specified branch (see figure below).
Summary
In this article, I introduced revert and reset as ways to deal with making an incorrect commit while operating Git.
I believe many people use Git for daily development, but since revert and reset are not commands used every day, their respective behaviors can be difficult to understand when you actually need them.
Since most cases can be handled with the methods introduced here, I hope you find this useful when the need actually arises.
Discussion