Git is undoubtedly one of the best version control tools today. However, some features provided by it still are confusing for beginners, especially when it comes to manipulations of the directory tree and the various "status" of files in it. So, I thought about making this post talking about different ways to undoing changes in a git repository in accordance with the situation.
First of all, it is necessary to understand one of the basic concepts of git called "Three Trees". Basically, the directory tree of a git repository has three states, 1) the working directory which is the current file system you are interacting with directly, 2) the staging area which are the files that were selected for the next commit and 3) the history of commits which is the file system saved in the repository through snapshots.
This is a base concept for understanding git. For a more detailed explanation, I suggest this Bitbucket tutorial.
Commonly we are faced with situations where we want to reverse changes committed in the project, and these situations can be quite varied. To know what command is more appropriated to undo the changes which you desire, it is important to know well your reason for you want to undo these changes on the project. Come discuss some possibilities.
- Your more recent modification has caused an error or a regression of the project, so you need to only undo the recent changes discarding it.
- You found a better way to solve a problem and want to redo something keeping this moment on commit history.
- You committed several files, and you want to undo this commit and do a commit with fewer files.
- Your commit made a snapshot of changes in a set of files, but you want to restore the content of a specific file no removing the commit.
There are several ways to undo changes/commits with git. I tried to consider the most basic methods to make the post accessible to all levels of git users. With three simple commands, it is possible to make the most diverse manipulations in the "Three Trees" of git, these are:
git reset and
Think on the commits as steps of a ladder, where you can take steps forward and steps backward. Some commands allow you to manipulate the
HEAD (the pointer that set the current structure of directory) to "walk" through the commits, undoing changes on the directory and letting you made new changes from past commits by overwriting the recent commits.
This command is used to change the
HEAD reference. It is very used to switch for another branch, but also may be used to move the
HEAD to another commit in the same branch.
git checkout [commit-hash], the
HEAD is pointed to the commit specified by hash. This command also may be used to undo changes not stagged in one specific file using
git checkout -- <file>.
Using this command is possible made the directory "forget" all changes of the specific commits (using the
--hard flag) or keep the modifications made by the commits on the staged area (using the
--soft flag). The main difference between
git reset and
git checkout commands are the
checkout move only the
HEAD ref, while the
reset allows moving all working tree - inclusive the current branch - to the specified commit, breaking the commit history continuity.
A simple example of usage is setting the number of commits for jump backward. Using
git reset [commit-hash] HEAD~4, the state of the working directory is changed to 4 commits backward from the actual (Pointed by HEAD).
Using this command, you reverse the changes of the last commit and create a new commit to save this reversion. So, you will keep the reversed commit in the history of your project.
Knowing these commands and their basic operation, we will look for the most appropriate method of undoing changes for each of the situations mentioned above.
Your more recent modification has caused an error or a regression of the project, so you need to only undo the recent changes discarding it.
In this case, I use:
$ git reset --hard <commit-hash>
--hard flag reverses all changes directly on the working directory, thus the modifications are forgotten immediately.
OBS: This command with the
--hard flag is considered dangerous because you can't recovery your modifications.
You found a better way to solve a problem and want to redo something keeping this moment on commit history.
In this case, I use:
$ git revert <commit-hash>
After this command a new commit is created on the commit history, allow to identify where the changes were reversed and if I want to recovery these changes, I have them in the commit history.
You committed several files, and you want to undo this commit and do a commit with fewer files.
This is interesting for reorganizing the next commits separating the files in several commits. In this case, I use:
$ git reset --soft HEAD~2
After this command, the state of the working directory such as the current branch is changed to two steps backward, the current state (step 1) and state of the last commit (step 2), setting the state to one commit before the last commit keeping the more recent changes in the working directory (because the
--soft flag) allowing to commit it as I desire.
You made a commit that made a snapshot of changes in a set of files, but you want to restore the content of a specific file no removing the commit.
In this case, I use:
$ git checkout <commit-hash> <file>
After this command, the file
<file> will be reversed to its state in the commit
<commit-hash> and will be staged for the next commit automatically
It is very important to know the situation that led to the need to undo something. Here, simple examples have been shown.
I believe that looking for simple solutions to recurring problems is a good starting point for starting studies of any technology, and with git, it is no different.
However, there are always more complete and complex ways to work. For an in-depth look at git, I recommend the official git documentation and Atlassian tutorials.
I hope this post can answer someone's doubts.
Thanks for reading ;)