Git Undo Commit: How to Undo a Commit in Git with Code Sample?

Commands for Git undo commit are git restore, git commit –amend, git revert, git reset, and git rebase. Learn how to undo local commit, last commit, rewrite history,...

Git Undo Commit

Git is a powerful version control system, but like any powerful tool, it's easy to make mistakes. Whether we've committed the wrong files, introduced a bug, the feeling of "Oh no, what have I done?" is familiar to many developers. Fortunately, Git provides a robust set of tools and commands to help us undo those mistakes, allowing you to rewind, revise, and recover your work.

This guide will explore common Git incidents and demonstrate the techniques we can use to effectively undo them, from simple file modifications to more complex history rewrites, so we can confidently keep our project on track.

>> Explore more:

Command: git restore

Discard all local changes in a file

Sometimes, we write some lines of code in a local file, haven’t committed them, and then realize they are not the best work. We want to undo them and get back to the last commit. In this case, we can use:

markup
git restore <filename>

Notice:

  • Discard uncommitted local changes cannot be undone!
  • In the case we accidentally delete a file, we can also use this way to restore.

Restore a chunk in a file

In equivalent with the case above, but now we just want to discard chunks or lines of code in a file. We can use the command with an additional option “-p” followed by the filename of the selected file.

markup
git restore -p <filename>

Git will sequentially go through each chunk (or hunk) in the file, we just need to choose the one we want to discard and type “y” indicating “Yes” to approve.

For example, text_A.txt was changed like this:

text_A.txt was changed

Enter this command “git restore -p text_A.txt”. The following question will be shown.

Enter this command “git restore -p text_A.txt”

It is asking us for approval of discarding that hunk. OK, type “y” and enter. Now, the hunk is back.

the hunk is back

Reset the file to its old version

Sometimes, we need to restore a file from commits’ changes to a specific version. We can run this command:

markup
git restore --source <git_commit_hash> <filename>

For instance, we have a commit that changed 2 files

a commit that changed 2 files

But the change at text_A.txt is wrong, so we need to reset it while still keeping the change at html_B.html. Let’s type:

markup
git restore --source de06f6a0f03b7611507bab3f2b8a0ce7e65e56f9 text_A.txt

de06f6a0f03b7611507bab3f2b8a0ce7e65e56f9 is the commit hash of an old version that we need the file to rollback to.

The result is that code in text_A.txt is automatically changed to its old version.

code in text_A.txt is automatically changed to its old version

Notice:

  • This change is still unstaged.
  • Git history is not rewritten.

Command: git commit –amend

If we commit our change too soon and want to make some changes in the latest commit, the option “--amend” can help.

For example, we have the latest commit that creates the file html_B.html.

the latest commit that creates the file html_B.html

But the title tag is wrong, so we want to fix that quickly just in the existing commit.

the title tag is wrong

So, commit the new change with the “--amend” option.

commit the new change with the “--amend” option

Cool, the new change was combined with the latest commit.

Notice:

  • We can also edit the commit message by using the “-m” option in “git commit --amend”.
  • “--amend” actually rewrite the git history, so we should not do it this way if we pushed the commit to our shared remote repository. Many conflicts will occur and make other team members confused.

Command: git revert

If we want to remove a commit in the middle of git history, we should use this:

markup
git revert <git_commit_hash>

Basically, this creates a new commit that removes all changes of the commit corresponding with the provided commit hash.

Notice:

  • Of course, “git revert” can cause conflict, so we have to resolve all available conflicts to make reverting done.
  • This approach does not rewrite git history, so it’s safe to share the revert commit to remote repositories.

Command: git reset

Reset to an old commit

When we see that all the latest commits are unnecessary, we can delete all those commits to get the project back to a specific commit. This is the case when “git reset --hard” comes to the play.

markup
git reset --hard <git_commit_hash>

We have all the commits here.

 all the commits

The latest one should be removed. So enter the command:

markup
git reset --hard de06f6a0f03b7611507bab3f2b8a0ce7e65e56f9

Nice, now that commit is gone.

that commit is gone

Recover deleted commits

As mentioned above, we can reset to any commit with “git reset”. But imagine that we accidentally do that command, our git history is now no longer keeping the deleted commits, and all the changes are gone away. Don't panic! We can fix this easily to bring all the commits back.

For example, we have this git history.

git history

Now, try to reset to “Create a HTML file” by

markup
git reset --hard de06f6a0f03b7611507bab3f2b8a0ce7e65e56f9

New git history is:

New git history

Until now, we have a tool to see everything has changed within our git repository. That is “reflog”. Let’s run:

markup
git reflog

Here is the result:

Here is the result of git reflog

We can see all git activities. Just notice that the latest commit before we do “git reset” is “Restore A”. Copy the hash “9a35adf” that lies right before “de06f6a” (the result of our incident “reset”).

Run the command:

markup
git reset --hard 9a35adf

The git history should be recovered successfully, all code changes are also back. Very interesting!

Command: git rebase

We need to keep in mind these steps when work with “rebase”:

  • How far back do you want to go? What should be the “base” commit?
  • Do rebase. An example: git rebase -i HEAD~3
  • In the editor, only determine which actions you want to perform. Don’t change commit data in this step.

Edit old commit message

We have this git history.

git history

Now, we will try to edit the commit message “change both A and B” to “update text file A and html file B”.

First, we will select commits for rebasing session by the command:

markup
git rebase -i HEAD~3

The editor will show the 3 latest commits and we can interact with them.

HEAD~3: The 3rd latest commit is going to the base.

3 latest commits

Change the action of the commit we are going to change its message to “reword”. Save change and close the editor.

change its message to “reword

Now, a new editor will appear to allow us to edit the commit message.

a new editor will appear to allow us to edit the commit message

Edit it, then save change and close. The change will be approved and we should get our target successfully.

The change will be approved

Delete commits

By the same way, but with the action “drop” we can remove commits.

the action “drop” can remove commits

The result after saving the action change is

The result after saving the action change

Squash multiple commits into one

Still in the same way, now, with the action “squash”, we can combine the commit we applied “squash” with its prior commit.

the action “squash

Save the change and close the editor. We will go to another editor to edit the message for the combination commit.

edit the message for the combination commit

We can write anything and save to complete rebasing “squash”. The result now is:

complete rebasing “squash”

Add changes to an old commit

We already learned about “git commit --amend” that can fix the latest commit. But now, we are going to repair a commit in the middle of git history. Let’s see how we can deal with that.

For example, we have a HTML file that was committed before, but now we see that it is wrong with “Hello World” in the div tag (commit de06f6a), and should be replaced with the h1 tag.

a HTML file that was committed before

Firstly, we need to commit the new change (replacing with h1) but of course not by the normal “git commit”. We will use:

markup
git commit --fixup de06f6a

de06f6a: the commit we are going to fix by committing the new change.

A new commit is created with the new change.

A new commit is created with the new change.

Secondly, rebasing with the base must include the fixed commit de06f6a and the option “--autosquash”. 

markup
git rebase -i HEAD~3 --autosquash

The editor now will be shown.

The editor now will be shown

With the “--autosquash” option, we don’t need to do anything. Git helped us mark the action “fixup” for our fix-up commit and also sort it right after the fixed commit “create a HTML file”. Great! We just need to close the editor to make rebasing complete.

The result now is this. The div tag was replaced by the h1 tag and still keeps the commit without changing its message and position.

The div tag was replaced by the h1 tag

Nice! We already fixed up a commit that can be anywhere in git history.

Conclusion

Making mistakes is a part of the development process, and thankfully, Git offers a robust set of tools to address them. Remember that understanding the implications of each command, especially those that rewrite history, is crucial.

Using reflog when experimenting with history-altering commands is highly recommended. By mastering these techniques, we'll be well-equipped to handle any Git-related mishap, ensuring a smooth and productive development workflow.

The key takeaway is that Git offers a path to recovery for almost any situation. The more you practice and understand these commands, the more confident and efficient you'll become in managing your projects.

>>> Follow and Contact Relia Software for more information!

  • development