Git How to undo a commit to remove some modifications

eye-catch Other techs

We often need to remove commits during our development. Let’s learn how to revert/remove commits.

Sponsored links

Create a History

Let’s create a git history first from scratch.

$ git init
Initialized empty Git repository in ./test/git-test/.git/

$ ls

$ echo aaaaa > a.txt
$ echo bbbbb > b.txt
$ echo ccccc > c.txt
$ ls
a.txt  b.txt  c.txt

I created 3 files. Then, commit the files one by one.

$ git add a.txt && git commit -m "Add a.txt"
$ git add b.txt && git commit -m "Add b.txt"
$ git add c.txt && git commit -m "Add c.txt"
$ git log --oneline
7178969 (HEAD -> master) Add c.txt
89918b9 Add b.txt
457bb83 Add a.txt

Okay. It’s ready to test.

Sponsored links

Git Revert

git revert is a command to revert commits. It undoes the specified commit but keeps the commit message to revert it. The command representation is the following.

git revert <commit_hash>

Revert an arbitrary commit

Let’s try to revert a commit. We need to check the commit number in advance.

$ git log --oneline
7178969 (HEAD -> master) Add c.txt
89918b9 Add b.txt
457bb83 Add a.txt

$ git revert 89918b9
[master 5268857] Revert "Add b.txt"
 1 file changed, 1 deletion(-)
 delete mode 100644 b.txt

$ git log --oneline
5268857 (HEAD -> master) Revert "Add b.txt"
7178969 Add c.txt
89918b9 Add b.txt
457bb83 Add a.txt

$ ls
a.txt  c.txt

The b.txt is removed.

Revert a previous commit

When we do something wrong with the last commit and want to remove the change, we can execute the following command.

$ git revert head
[master ffb2145] Revert "Revert "Add b.txt""
 1 file changed, 1 insertion(+)
 create mode 100644 b.txt

$ git log --oneline
ffb2145 (HEAD -> master) Revert "Revert "Add b.txt""
5268857 Revert "Add b.txt"
7178969 Add c.txt
89918b9 Add b.txt
457bb83 Add a.txt

$ ls
a.txt  b.txt  c.txt

head keyword can be used instead of a commit number. It’s ok to use capital letters. It’s useful to use it if you know where the desired commit is.

Revert multiple commits

It’s also possible to revert multiple commits at once.

$ git revert ffb2145 457bb83
[master f27055b] Revert "Revert "Revert "Add b.txt"""
 1 file changed, 1 deletion(-)
 delete mode 100644 b.txt
[master c06a936] Revert "Add a.txt"
 1 file changed, 1 deletion(-)
 delete mode 100644 a.txt

$ git log --oneline
c06a936 (HEAD -> master) Revert "Add a.txt"
f27055b Revert "Revert "Revert "Add b.txt"""
ffb2145 Revert "Revert "Add b.txt""
5268857 Revert "Add b.txt"
7178969 Add c.txt
89918b9 Add b.txt
457bb83 Add a.txt

$ ls
c.txt

There are too many reverts. It’s a messy history. Revert of revert might not be a good idea.

Remove the previous commits by git reset

Revert command can undo a commit with a commit message. It’s not suitable if we want to remove the commit itself. If the target commits are the Head of the history or consecutive commits from the Head, we can use reset command.

git reset command with --hard can remove the commits. Let’s remove the recent two commits.

$ git log --oneline
c06a936 (HEAD -> master) Revert "Add a.txt"  <-- want to remove
f27055b Revert "Revert "Revert "Add b.txt""" <-- want to remove
ffb2145 Revert "Revert "Add b.txt""     <--- want to go here
5268857 Revert "Add b.txt"
7178969 Add c.txt
89918b9 Add b.txt
457bb83 Add a.txt

$ git reset --hard HEAD~2
HEAD is now at ffb2145 Revert "Revert "Add b.txt""

$ git log --oneline
ffb2145 (HEAD -> master) Revert "Revert "Add b.txt""
5268857 Revert "Add b.txt"
7178969 Add c.txt
89918b9 Add b.txt
457bb83 Add a.txt

The commits are removed correctly. We can also specify the target commit number instead.

Remember that git reset --hard <commit_number> can be used only if we want to remove the consecutive commit(s) from the top. It can’t be used to remove a middle commit.

Remove some commits by git rebase

Another way to remove commits is use git rebase. Be careful to use git rebase because it changes the commit numbers in the history. After executing git rebase, the change must be pushed with git push --force command.

Let’s add an additional file.

$ echo ddddd > d.txt && git add -A && git commit -m "Add d.txt"
warning: in the working copy of **d.txt**, LF will be replaced by CRLF the next time Git touches it
[master ee8c440] Add d.txt
 1 file changed, 1 insertion(+)
 create mode 100644 d.txt

$ git log --oneline
ee8c440 (HEAD -> master) Add d.txt
ffb2145 Revert "Revert "Add b.txt""  <-- remove
5268857 Revert "Add b.txt"  <-- remove
7178969 Add c.txt
89918b9 Add b.txt  <-- remove
457bb83 Add a.txt

d.txt is necessary to keep. We want to remove b.txt and the two revert commits. The command looks like this.

git rebase -i <commit number used as a base>

The commit number is the commit where we want to start the rebase. Since we need to remove b.txt, we need to choose one commit before, which is the first commit in this case.

$ git rebase -i 457bb83

Once it’s executed, an editor is open. We can change the command as described below.

pick 89918b9 Add b.txt
pick 7178969 Add c.txt
pick 5268857 Revert "Add b.txt"
pick ffb2145 Revert "Revert "Add b.txt""
pick ee8c440 Add d.txt

# Rebase 457bb83..ee8c440 onto 457bb83 (5 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
#         create a merge commit using the original merge commit's
#         message (or the oneline, if no original merge commit was
#         specified); use -c <commit> to reword the commit message
# u, update-ref <ref> = track a placeholder for the <ref> to be updated
#                       to this position in the new commits. The <ref> is
#                       updated at the end of the rebase
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

To remove the target commits, set d for them.

d 89918b9 Add b.txt
pick 7178969 Add c.txt
d 5268857 Revert "Add b.txt"
d ffb2145 Revert "Revert "Add b.txt""
pick ee8c440 Add d.txt

Then, the commit history gets cleaner than before.

$ git log --oneline
f7c51c9 (HEAD -> master) Add d.txt
ae8adda Add c.txt
457bb83 Add a.txt

$ ls
a.txt  c.txt  d.txt

Related Articles

Comments

Copied title and URL