How to Undo and Redo Changes in Git
Table of Contents
- Discover how to use revert, cherry-pick and reset in Git
- A few important definitions
- Undoing changes
- Redoing changes
- Have we lost all the logs?
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 createduntracked
- 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 commitstaged
- changes are in Git and ready to be committed using the commandgit add
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 readme.txt
file.
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
Commit hash
Both outputs show the commit hash for the last change where we added the readme.txt
file.
git log
= f41dcd7d75bf3a883fb2bfd6073bc149878065c4git 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.
Undoing changes
Git allows you to undo commits and changes multiple ways, but we'll concentrate on revert
and 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 commitreset
– a reset moves the head to point to a referenced commit or a particular file that has been staged but not committed
Revert (undo)
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 readme.txt
file.
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 --abort
option.
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.
Redoing changes
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
Reset (index-level)
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 usinggit add
commit-level
- is when staged changes have been committed to the repository using the commandgit commit
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
A 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 --hard
option.
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 detailsgit 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
The git reflog
command is the equivalent of using the option --oneline
with 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.d
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.