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.
The "Three Trees" concept
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.
When I need to revert changes?
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.
Commands to undo changes
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 checkout
, git reset
and git revert
.
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.
git checkout
:
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.
Using 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>
.
git reset
:
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).
git revert
:
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.
Applying on the different situations
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>
The --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
Remarks
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 ;)
Top comments (3)
I like keeping a clean history a sometime that makes for merge conflicts when rebaseing.
I had an older which had changes I wanted gone. The commits later all belonged in the one commit. I could have just manually reverted. Instead, I go back with interactive rebase, edit the commit using amend to pull out the changes.
Since I don't want later merge conflict I commit these edits I don't want. I let the rebase finish. Then revert the commit I don't want.
Now I reset the branch softly all the back to the changes I don't want. I use ament to adjust the commit and message.
I know, lots of steps, but know the tools I apply stages that create a fairly streamlined edit where I can work to the end state I want and end up with a clean history. If the revert had conflicts, I would be working at the end state goal rather then conflicts within the rebase actions.
Oh cool, of course. Keep a clean commit history is important. The deeper the knowledge of the tool, the more options you have to deal with these situations.
For example, I think when there are many branches, other situations that I didn't mention here arise. I rarely use
rebase
, only in well special cases, I think it is a higher level than what I wanted to show.But is a cool command :D
I think it was important your article didn't go into rebase.
I just know that I used to create a poor man's rebase interactive and so happy to have found it.
My old git processes
Jesse Phillips ・ Dec 20 '18 ・ 1 min read