How to Undo and Redo Changes in Git

Table of Contents


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 git 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

git log

Require less detail? Use the option --oneline. It outputs a summary of each commit on one line.

git log --oneline

git log --oneline

Commit hash

Both outputs show the commit hash for the last change where we added the readme.txt file.

  • 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.


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 commit
  • reset – 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

git revert --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

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

git revert

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>

git cherry-pick

Again, the logs show a new commit has been created.

git log --oneline

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 using git add
  • commit-level - is when staged changes have been committed to the repository using the command git commit

git revert flow.png

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

echo date

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

git reset head

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

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

git log final

Let’s go back to the initial commit.

git reset --hard <commit hash>

git reset --hard final.png

Checking the log and current directory shows the state of our initial commit.

git log initial --hard.png


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

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

git log -g snippet

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

git reset --hard commit hash

Now we have the readme.txt file back, let’s recheck the logs.

git log final

There we have it! We have restored the previous state, which appeared to be lost.