We, programmers, are human beings ( kind of…). And like any human being we make mistakes; a lot!
Some mistakes have huge repercussions and can certainly require lots of time and energy to correct. But if we have the good habit of initializing Git on any project, we can always go back in time and remove/correct the unwanted parts.
With Git, we have some commands that can be used as a time traveler. Those commands allow us to rewrite the history of the commits, change the message of a commit, set the project to a previous state…
But time-traveling always carries the risk of worsening problems or creating new ones, right Barry?
We will see together how to use those commands, how they can be useful, and how they might get us fired.
What is that we call the history?
Now before we go any further we need to know about three concepts that really help when 'playing' with history.
Those concepts are history, HEAD, and Index.
The history on Git lists the various commits made on the project, in chronological order. It keeps track of the different actions with the name of their author and the date.
We have already got a glance of that history with the command git log, in this article.
git log --oneline
As you can see in the picture above, we have, at this stage, a list of commits that tell the history of our "amazing" project.
Rewriting this history, therefore, consists of editing this register in different ways.
The two other concepts are more abstract. But understanding them will help better understand what happens when we edit the history.
The HEAD with git represents the current branch or the last commit of the current branch. So when we move from one branch to another, we are changing the pointer for HEAD. And when we made a commit, the HEAD moves to point to that commit.
The index is where we place files we want to commit. This is the space where we will find all the staged files. Each time we are using the
git add -a command, it will place the last edited files to the Index.
Here, I will be using the same project we used in the get started with Git article. You can find that project on GitHub. You can clone it with the command.
git clone https://github.com/webamadou/git-101.git
git commit --amend
The first case we will go through is the one with a wrong commit message.
The message of a commit is very important. It helps the team understand what was done at each step of the project.
Whenever a mistake is made on the last commit or we feel the need to add more details, we can use the "amend" command to edit the message on the most recent commit.
git commit --amend
That will open the text editor with the message of the last commit. We can then edit that message and save it.
Let's use the log command to list all our commits.
git log --oneline
Now let's change the last commit message with the
The checkout command is mostly used to switch from one branch to another. But it also can be used to navigate through commits. Meaning, that can be used to set our project in one of the previous versions. We need to use the id of a commit to achieve that.
git log --oneline we can list all the commits and pick the state we want to set our project back to.
On our git_101 project let's set the navbar back to black.
git log --oneline
git checkout 3109a81
As you can see on the picture the id of the commit before we changed the navbar is 3109a81. We then used that id on the checkout command and the navbar is set back to dark.
Use the command
git checkout masterto get back to the 'normal' state.
Another way to use the checkout command is with the name of a file.
git checkout commit_id file_name
That variation of the checkout command will switch back the specified file to a commit without changing the HEAD, meaning without taking the whole project to that commit. The file will be changed to its state in the past, but the HEAD will remain the same.
If this is applied to our project, that means the navbar will be back to dark mode, but the other files of the project will remain unchanged.
With that case, we can then make changes and make a new commit.
The git revert command is going to undo actions on the given commit. It will create a new commit and will remove all the actions that were made at some point.
It's like you go back in time to delete that email you sent but still keep a log saying you deleted that email. (I know, that sounds crazy.)
This is a useful command when you have a wrong commit that you want to get rid of. But since it will delete actions of the past we need to be sure that there are not parts of our code that are linked or depended on those actions.
If used wrong we might end up messing up the project. Even if it can be used to go deep back in the past, it is mostly a command to undo the last commit since going far into the past commits might trigger fatal errors when dependencies are deleted.
In our project let's remove the README.md file. The command
git log --oneline tells us that the README.md was created at commit dca9335.
We can then use the following command to undo the creation of the README.md
git revert dca9335
That will open the editor to allow us to add the message for the new commit. We can then, add the needed specifications.
And if we do
git log --oneline we will notice the creation of a new commit.
The Git reset command is certainly among those that have great control over the past actions. However, it can bring big messes upon ou project if used in an inappropriate way.
The basic use of this command is to remove the last staged files.
Used on it's simplest way, as shown above, the command will simply remove file from the stage area. It can be handy if you added a wrong file in the staged files.
If it's only one file we need to remove from the staged, we can add the name of the file to that command.
git reset filename.extension
Keep in mind this is not going to undo the changes made on the file. It will only remove the file from the index (stage file zone).
Another way to use the reset command is with the id of a commit.
git reset commit_id
Used this way, the command will move the HEAD to the given commit. Meaning, it will rewrite the history of the current branch by removing all commits made after the specified one.
git reset command has three options that give different results.
git reset –soft commit_id
The soft option is the default one. It is the one that applies when no option is used.
That will do one action that is to move the HEAD to the commit.
git reset –mixed commit_id
The mixed option will remove the head to the given commit and unstage all files. It acts like the
--soft option, but will also remove files in the Index.
Both the soft and the mixed options are not impacting the last changes we made. The HEAD is moved to the specified commit (or branch) but the last changes we made are not affected.
That is not the case of the next option.
git reset –hard commit_id
That command is powerful and must thus be used wisely.
First, it will move the HEAD to the commit_id. Then it removes all the files in the index. And will also undo all last changes.
THIS ACTION IS IRREVERSIBLE
If we don't provide a commit id (or the name of a branch), the command
git reset --hard will just remove the last changes on the current branch. That might sound risky but it can actually be helpful in some cases.
Let's say while working on functionality we found ourselves surrounded by bugs and are overwhelmed. Or we found out that our approach is completely wrong. Using the
git reset --hard command will take us to the last stable version of our code where we could safely handle the functionality another way.
The rebase command is my preferred one.
It is mainly used to put some order on the history line.
What this command does is it applies changes of one branch to another one and will also rewrite the history.
It changes the base of a branch and makes it look like it was created from that base.
An example should be more meaningful.
To better visualize the actions of the rebase command, we can use a Git GUI. The one I use is Gitkraken; you can download it here.
After installation, link Gitkraken with your Github account and open the local git-101 repository with it.
Let's say we created two branches name source_1 and source_2 both based on the master branch.
git branch source_2
git branch source_1
We switch to source_1 to make some edit and a commit.
git checkout source_1
Change the website title.
git commit -am "Edit the title of the website"
Then switch to source_2 and do the same.
git checkout source_2
Change the menu
git commit -m "Save the edit on source_2"
Now we have two branches diverging and both are based on the master branch. Using the rebase command we can integrate all the changes from source_1 into source_2. That will give the illusion that source_2 were created from source_1 thus rewriting the history.
git rebase source_2
This command is mainly used to maintain a linear project history. That is to have a history that follows one timeline with no diversion.
Making mistakes is part of programming. But as long we are using Git efficiently, we can avoid getting ourselves banned from the dev team.
Still, rewriting history is not recommended on Git. This is an option that should be used as a last resort.
Each of the command seen here offers more options. Here are some links that give more details on how to edit the Git history: