>

When working in a software project whose code base is managed by Git, undo is a very common and frequent operation,
and also it is one of the most attracting feature in Git. For better reference, I summarize all the situations that may need undo:

1. Drop unsaved changes


$ git checkout -- filename

Note:
If you have conflict changes on this file and not merged yet, you may encounter errors saying “error: path ‘some/path’ is unmerged
“, we can reset the file and then checkout it to drop the changes, e.g.

$ git reset filename
$ git checkout -- filename

drop all files:

$ git checkout -- .

Note that the above commands won’t remove those newly added files that are in untracked status, to remove them also, use this:

$ git clean -df

Options explannation:

  • -d Remove untracked directories in addition to untracked files
  • -f Force (might be not necessary depending on clean.requireForce setting, clean.requireForce is usually true by default)

Note:
you can archive the current changes for later use by git stash commands:

git stash save or git stash -u
git stash pop

2. Undo add

The following commands can bring back files added into stage by git add ..

$ git reset
$ git reset HEAD
$ git reset HEAD .

Note:
Although these commands can bring files back to workspace and keep its changes, they are not quite practically necessary,
as if we add the files into stage, and then want to modify them again, we just simply modify them,
the files will be unstaged automatically.

3. Undo commit

(1) Get back from commit to stage (may not be quite useful)

$ git reset --soft HEAD^

(2) Get back from commit to workspace (unstaged)

$ git reset HEAD~

or

$ git reset --mixed HEAD^

(3) Drop the commit and its all changes

git reset --hard HEAD~1

Demo for (2):

HEAD is just a pointer to the latest commit.
In the below diagram, the HEAD points to commit C, (F) is the state of our files.

   (F)
A-B-C
    ↑
  master

After we undo the commit(by doing git reset HEAD~), the result becomes:

   (F)
A-B-C
  ↑
master

As we can see, by executing git reset HEAD~1, Git moves the HEAD pointer back one commit. and leave the files with the changes as they were. We won’t lose anything.

Demo for (3):

Suppose we have a status like below, C is the HEAD and (F) is the state of our files.

   (F)
A-B-C
    ↑
  master

After we execute nuke the commit(by doing git reset --hard HEAD~1), the result becomes:

 (F)
A-B
  ↑
master

Now we can see that B is the HEAD, and we won’t be able to see the commit again after we nuke it.

But we still have a way to get it back, use git reflog to check the commit history

c59da30 HEAD@{0}: reset: moving to HEAD~1
993a1cd HEAD@{1}: commit: test undo commit1
c59da30 HEAD@{2}: reset: moving to HEAD~
abcedbd HEAD@{3}: commit: test undo commit
c59da30 HEAD@{4}: rebase finished: returning to refs/heads/master
c59da30 HEAD@{5}: rebase: x22

as shown, we can get the ref id from there, then execute below command to get to the commit:

$ git checkout -b tmpBranch abcedbd

Since we use a hard reset, it becomes a detached commit.

More:

We can alway use git checkout commit_it to temporarily get to a commit and come back using git checkout master. If we want to make commits while we’re there, go ahead and make a new branch there:

$ git checkout -b old_state 0d1d7fc32

A read world example for (2):

Suppose we accidently committed a unexpected file and we want to get it back, modify, then commit the rectification.

A proper workflow may look like this:

$ git reset HEAD~

... edit files ...

$ git add .
$ git commit -c ORIG_HEAD

Note:
commit -c ORIG_HEAD helps us reuse the old commit message.
It will open an editor, which initially contains the log message from the old commit,
this allows us to edit it. If we do not need to edit the message, can could use the -C option.

4. Modify/Merge commit message

Modify the latest commit message:

$ git commit --amend -m "New commit message"

If we have just made a commit, but realize that we forgot to add some files that need to added immediately,
or simply need to modify some file, just do:

git add missing_file.txt
git commit --amend --no-edit

If we have multiple commits (haven’t pushed yet) in local, and we want to squash those commits to one,
we can use interactive rebase:

$ git rebase -i HEAD~X

or

$ git rebase -i commit_id

Note: X is the number of commits to the last commit you want to be able to edit.

5. Revert the pushed commit

Once we have pushed the local commits onto the remote repo, we won’t be able to get back using any of the above command,
in that case, we should use git revert.

First, we need to use git log to find out the commit it that we want to revert back to, then use:

$ git revert c011fb8

or revert the last two commits:

$ git revert HEAD~2..HEAD

or revert all commits to a specific commit_id:

$ git revert --no-commit 0317e074..HEAD

The --no-commit flag lets git revert all the commits at once- otherwise you’ll be prompted for a message for each commit in the range, littering your history with unnecessary new commits.

If one of the commits between 0317e074..HEAD is a merge then there will be an error popping up, in that case we could do: git revert -m 1 HEAD. check here.

Note:
Before we executing git revert, we need to make sure the working tree is clean, which means there is no uncommited changes.
the theorem behind the git revert is that it creates a new commit with the reverse patch.





Reference

https://github.com/blog/2019-how-to-undo-almost-anything-with-git