Discover how to use revert, cherry-pick and reset in Git
Continuing from the last blog, How to Build a Git Server, we'll be building on the repository. In its current state, the repository only contains a .gitignore file. We'll go over how to manage the files in the repository should we need to roll back a previous change and reinstate it again.
A few important definitions
Tracked and untracked files
tracked- are files that have previously been committed or at least the files that have been staged, if they are newly created
untracked- are files that have been created but remain unstaged, nor have they been committed in the past
Staged vs unstaged changes
unstaged- are changes tracked by Git but not marked for commit
staged- changes are in Git and ready to be committed using the command
List committed changes
Listing all the committed changes is done using the command
git log. It provides a detailed output, including user, date-time and message.
But first of all, let's add another commit by creating an empty
touch readme.txt git add . git commit -m “Added readme.txt” git log
Require less detail? Use the option
--oneline. It outputs a summary of each commit on one line.
git log --oneline
Both outputs show the commit hash for the last change where we added the
git log= f41dcd7d75bf3a883fb2bfd6073bc149878065c4
git log --oneline= f41dcd7
We can use either hash to reference changes. However, the result when using the option
--oneline is easier when copying the commit hash and more pleasant to the eye.
Git allows you to undo commits and changes multiple ways, but we'll concentrate on
reset in this section.
Revert vs Reset
revert– will undo the changes of a specific commit, all other changes before and after remain creating a new commit
reset– a reset moves the head to point to a referenced commit or a particular file that has been staged but not committed
When reverting a change, it is applied instantly once entered. There isn't a prompt to commit the change.
git revert <commit hash>
A more forgiving approach is to use the
--no-commit option. The change is staged but not committed. Giving you the option to review the change and make further changes before being committed.
Let's use this option to remove the
git revert <commit hash> --no-commit
As you can see from the screenshot, the current state of the
git status command is the
readme.txt file has been deleted and staged, ready and waiting to be committed. But what if we change our mind and want to cancel the change. We can submit the
git revert --abort
Now we are back to normal with no changes made.
Let’s make the change for real this time.
git revert <commit hash> git log --oneline ls -la
There is no longer the
readme.txt in the directory, but we now have an additional commit.
Consider you want to redo a change and retrieve the
readme.txt file. The command
git cherry-pick is an option for undoing reverts. Again, it commits by default, unless
--no-commit is specified.
git cherry-pick <commit hash>
Again, the logs show a new commit has been created.
git log --oneline
There are two levels that
git reset works at, index-level (staging area) and commit-level.
- index-level – is when a file has been added to the staging area using
- commit-level – is when staged changes have been committed to the repository using the command
In this instance, we’ll use
git reset to unstage the file readme.txt, currently in the staged area. Let’s amend the file and add it.
echo $(date) >> readme.txt cat readme.txt git add readme.txt git status
Presently, the readme.txt file is staged and ready to be committed. If we no longer want to submit the change, we can reset the HEAD for the given filename to be unstaged.
git reset HEAD readme.txt
The result is the file was unstaged and kept the changes made. It is the default behaviour for
git reset, known as
--mixed. There are several different modes listed below:
--soft- doesn’t touch the index or working tree, just creates a new commit at the HEAD position
--mixed- (default) – resets the index and leaves the working tree. In other words, any changes made to the file remain the file is just unstaged
--hard– resets the index and working tree setting the state to the last commit
--merge- resets the index by undoing all staged changes. If there was a file that had not been staged but modified, those changes remain untouched. But, if there had been a change to a file that has been staged and further changes were made to that file but not staged, git reset will abort
So if we wanted to undo everything since the last commit, we’d specify the
--hard option. Let’s re-add the file and use the command
git reset with the
--hard option included this time.
git add . git status git reset --hard HEAD
Checking the status now, we can see all modifications were dropped.
Reset to a previous commit
git reset can also have the power to move the HEAD to a previous commit based on the commit hash. Again, the options are the same, but we'll use the
git log --oneline
Let’s go back to the initial commit.
git reset --hard <commit hash>
Checking the log and current directory shows the state of our initial commit.
Have we lost all the logs?
No, all the logs are still intact. We have to view the reflogs to obtain the details to use for recovery. The log output below is local to the repository.
git log -g, (
--walk-reflogs) - Instead of walking the commit ancestry chain, it walks the reflog entries listing newest to oldest commit details
git reflog- is a record of the HEAD’s state changes of your local repository. The default number of days this information is stored is 90 days. You can change it with:
git config gc.reflogExpire 180.days.ago
git reflog command is the equivalent of using the option
git log -g command.
Let’s get the readme.txt file back and finish up.
git log -g
Note the second newest commit has the message “Added readme.txt”. Copy the commit hash and
git reset --hard to this commit.
git reset --hard <commit hash> ls -la
Now we have the readme.txt file back, let’s recheck the logs.
There we have it! We have restored the previous state, which appeared to be lost.