Undoing commits in [.inline-code]git[.inline-code] is a common task that developers perform when they want to undo changes that they have made to a repository. Whether you made a mistake in a commit or want to go back to a previous version of the code, [.inline-code]git[.inline-code] provides you several ways to undo a commit.
[#git-reset-undo-commit]Using [.inline-code]git reset[.inline-code] to undo a commit[#git-reset-undo-commit]
Using [.inline-code]git reset[.inline-code] is the most common way to undo changes that have been made to a repository. Resetting is a way of moving the current tip of a branch to a previous commit and thus resetting it to a previous state. This can be done to remove commits from the current branch that are no longer needed, or to undo changes that have already been made.
To use [.inline-code]git reset[.inline-code], you need to specify the mode and the commit you want to reset the branch to. This takes the form:
For [.inline-code]<mode>[.inline-code] there are three flag options:
- [.inline-code]--soft[.inline-code] will reset back to the specified commit, but the changes made in the subsequent commits will remain as part of the working directory and will be staged to be committed.
- [.inline-code]--mixed[.inline-code] is the default flag which is used when no other flag is specified. With this, while the working directory is not changed, meaning that no files are changed, none of these changes are staged to be committed. This means that if you run git status you will see all the files that were changed are in red, waiting to be committed.
- [.inline-code]--hard[.inline-code] flag will change both the staged snapshot and the working directory to remove all the changes beyond the commit specified. This means that the changes made in subsequent commits will act like the other commits never existed.
And [.inline-code]<commit>[.inline-code] is then the specific commit you want to refer back to.
[#undo-last-commit-HEAD]You can undo your last commit(s) using [.inline-code]HEAD[.inline-code][#undo-last-commit-HEAD]
Which will reset the current branch back two commits and will remove all changes that had been made in the last two commits from the working repository.
[#git-reset]Save this [.inline-code]git reset[.inline-code] command for easy re-use later[#git-reset]
Warp has a predefined Workflow available that Warp terminal users can call by pressing CTRL-SHIFT-R and starting to type “undo most recent git commit”. One of very many Warp Workflows, this is an easier way to remember the syntax for undoing the last commit while leaving the working tree (the state of the files on disk) untouched:
A word of caution: be careful with using [.inline-code]git reset[.inline-code]. It will overwrite the commit history. While this isn’t a problem if you are undoing local changes, it can be difficult to untangle when the commits have already been pushed to a remote repository.
[#git-checkout-undo-commit]Using [.inline-code]git checkout[.inline-code] to undo a commit[#git-checkout-undo-commit]
Another way of undoing commits is to [.inline-code]checkout[.inline-code] a previous commit either for the whole repository or for a specific file. This is commonly used to checkout different branches of code but can also be used to checkout specific commits.
To checkout a specific commit, you need to specify the hash of the commit you want to checkout. This can be done using the command:
For this, replace [.inline-code]<commit_hash>[.inline-code] with the hash of the commit you want to check out. When running this command, `git` will switch the repository to the state that it was in on the specified commit.
The issue with this approach is that you will end up in a detached HEAD state. This means that you are not currently on any branch, so any changes you make will not be saved in a branch. If you want to make changes, you will need to create a new branch. This can be done using:
Where [.inline-code]<branch_name>[.inline-code] is the name of the new branch you want to create. From here you can start making new commits on this branch as usual.
[#undoing-changes-git-checkout]Undoing changes to a specific file with [.inline-code]git checkout[.inline-code][#undoing-changes-git-checkout]
Alternatively, you can undo changes to a specific file by [.inline-code]checking out[.inline-code] that version of the file from a previous commit. The command for this is:
Which will reset the file to that state from the specific commit. It is important to note that this is a destructive operation since it permanently discards any changes made to the file after that commit.
[#git-revert-undo-commit]Using [.inline-code]git revert[.inline-code] to undo a commit[#git-revert-undo-commit]
Another approach that could also be used to “undo” commits is [.inline-code]git revert[.inline-code]. [.inline-code]git revert[.inline-code] works to undo a change by creating a new commit. This makes it a safe way for undoing changes in a public repository, as it does not change the history. An example:
This will undo all the changes in the [.inline-code]<commit_hash>[.inline-code] commit and then will create a new commit with those changes undone. If there are no merge conflicts, an editor will open up asking you to name the new commit, and then it will be added to the end of the current head.
This approach preserves the commit history and potentially reduces the amount of merge conflicts that you may have to deal with compared to the use of either [.inline-code]git reset[.inline-code] or [.inline-code]git checkout[.inline-code].
[#undoing-commits-remote-repos]Tread carefully with undoing commits in remote repositories[#undoing-commits-remote-repos]
While the methods above seem simple when it comes to undoing commits in a local repository, this can become an issue when undoing commits in a remote repository. If you are using a remote repository and collaborating with others, changing that history can affect the development path taken and can create merge conflicts. The effect of this depends on the method you use:
- [.inline-code]git reset[.inline-code] overwrites commit history. This means that your history will be different to the remote repository. If other developers are using the commits you have undone, then this will cause conflicts. This is why `git reset` is generally not recommended for use with a remote repository unless you are working on your own development branch.
- [.inline-code]git checkout[.inline-code] can also overwrite commit history when you checkout a previous commit for the full repository. To resolve this issue, you will need to create a new branch and then resolve the merge conflicts when merging back into the main branch.
- [.inline-code]git revert[.inline-code] is the safest way of undoing commits as it does not overwrite the commit history. Instead it creates a new commit that undoes the changes introduced in a previous commit while retaining the history of the origin commit. Other contributors will be able to see the commit was reverted and adjust their work accordingly.
If you decide to use [.inline-code]git reset[.inline-code] or [.inline-code]git checkout[.inline-code] to overwrite commits, then you can use [.inline-code]git push --force[.inline-code] to overwrite the remote branch with your local branch. This will overwrite any commits that were made after your last pull. This can create problems if other contributors have made changes that conflict with those that you are pushing, so you need to make sure you communicate clearly with others and coordinate your changes carefully.
[#choose-approach]How do I decide which approach to use?[#choose-approach]
If you don’t care about commit history, you should use [.inline-code]git reset[.inline-code]. If you don’t care about keeping the same branch, then you can use [.inline-code]git checkout[.inline-code]. If you care about history and the branch structure, then you can use [.inline-code]git revert[.inline-code].