How I undo various Git operations

Introduction

When working with many Git repositories daily, things can go wrong. This is especially true during a fast-paced working day. Although the name Git implies, its usage is simple, undoing things can be tricky.

With this blog post, I'd like to share how I undo various Git operations. The article was written based on git version 2.45.2.windows.1. Also, I use posh git for the status prompt in PowerShell.

💡
The commands I share are thoroughly tested. Still, things can go wrong in your very special situation. So use them thoughtfully and at your own risk. Data loss might occur.

Undoing git commit

Scenario A: Adjusting a commit message

Goal

You'd like to change your last commit message or make additional changes to your last commit. You want to keep the changes you committed previously.

Pre-conditions

You've made changes and committed them to your local repository only and haven't pushed them to a remote yet. You are okay with a new commit hash replacing the previous one.

Solution

Use git commit --ammend to change your last commit.

C:\repo [branch1]> git log --oneline -n 1
4f0c90b (HEAD -> branch1) adds file1

C:\repo [branch1]> echo "foo" >> file1.txt

C:\repo [branch1 +0 ~1 -0 !]> git commit -am "wrong commit message"
[branch1 81a502f] wrong commit message
 1 file changed, 1 insertion(+)

C:\repo [branch1]> git log --oneline -n 2
81a502f (HEAD -> branch1) wrong commit message
4f0c90b adds file1

C:\repo [branch1]> git commit --amend -m "a better commit message"
[branch1 66e93d3] a better commit message
 Date: Tue Jul 9 22:42:41 2024 +0200
 1 file changed, 1 insertion(+)

C:\repo [branch1]> git log --oneline -n 2
66e93d3 (HEAD -> branch1) a better commit message
4f0c90b adds file1

Scenario B: Reverting a commit that hasn't been pushed yet

Goal

You've accidentally committed changes to your local repository that you'd like to undo. You'd like to keep your changes

Pre-conditions

You have committed the change to your local repository but haven't yet pushed them to a remote.

Solution

Use git reset --soft HEAD~1 to revert the previous commit.

C:\repo [branch1]> git log --oneline -n 1
66e93d3 (HEAD -> branch1) a better commit message

C:\repo [branch1]> echo "foo" >> file1.txt

C:\repo [branch1 +0 ~1 -0 !]> git commit -am "changes file1"
[branch1 39e5569] changes file1
 1 file changed, 1 insertion(+)

C:\repo [branch1]> git log --oneline -n 2
39e5569 (HEAD -> branch1) changes file1
66e93d3 a better commit message

C:\repo [branch1]> git reset --soft HEAD~1

C:\repo [branch1 +0 ~1 -0 ~]> git log --oneline -n 1
66e93d3 (HEAD -> branch1) a better commit message

C:\repo [branch1 +0 ~1 -0 ~]> git status
On branch branch1
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   file1.txt

Undoing git merge

Scenario A: Revering a merge that is not pushed yet

Goal

You'd like to revert a prematurely made merge

Pre-conditions

Your local branch hasn't yet been pushed to a remote. There is nothing to commit on your local branch, and your working tree is clean

Solution

Use git reset --hard HEAD~1 to undo the merge and reset to the previous commit hash.

C:\repo [branch1 ≡]> git merge branch2
Updating 58bf207..cb93d0d
Fast-forward
 file3.txt | 5 -----
 1 file changed, 5 deletions(-)
 delete mode 100644 file3.txt
 
C:\repo [branch1 ↑1]> git reset --hard HEAD~1
HEAD is now at 58bf207 changes file1

Undoing git push

Goal

You accidentally pushed to a remote and would like to undo the changes.

Pre-conditions

You ensured no other user of the repository is fetching the incorrect changes or is trying to build on top of the commit.

Solution

Use git push origin --force <hash>:<branch> to reset the remote.

C:\repo [branch1 ↑1]> git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 275 bytes | 275.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/matthiasguentert/git-article.git
   400c7dc..2d9687c  branch1 -> branch1
   
C:\repo [branch1 ≡]> git log --oneline -n 2
2d9687c (HEAD -> branch1, origin/branch1) updates file2
400c7dc deletes file3

C:\repo [branch1 ≡]> git push origin -f 400c7dc:branch1
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To https://github.com/matthiasguentert/git-article.git
 + 2d9687c...400c7dc 400c7dc -> branch1 (forced update)

Undoing git pull

Goal

You'd like to undo a git pull that merged changes from a remote into your working directory

Pre-conditions

You are okay to lose any staged changes

Solution

Use git reset --hard HEAD~1 to discard any local changes!

C:\repo [branch1 ↓1]> git pull
Updating 369c785..58bf207
Fast-forward
 file1.txt | 1 +
 1 file changed, 1 insertion(+)
 
C:\repo [branch1 ≡]> git reset --hard HEAD~1
HEAD is now at 369c785 some commit message

Undoing git add

Scenario A: Unstaging a staged file

Goal

You accidentally staged a file too many and would like to unstage it.

Solution

Use git restore --staged <file> or git reset HEAD <file>

C:\repo [branch1 +1 ~1 -0 ~]> git status
On branch branch1
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   file1.txt
        new file:   file2.txt

# With git version >= 2.23.0 use git restore to unstage the file
C:\repo [branch1 +1 ~1 -0 ~]> git restore --staged file1.txt

# ... or git reset as an alternative
C:\repo [branch1 +1 ~1 -0 ~]> git reset HEAD file1.txt

C:\repo [branch1 +1 ~0 -0 | +0 ~1 -0 !]> git status
On branch branch1
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   file2.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   file1.txt

Unmodifying a modified file

Goal

You accidentally made changes to a file that you'd like to revert to the last commit

Pre-condition

You are aware and okay to lose your changes fully! The file you'd like to restore is in a tracked state and known to Git.

Solution

Use git restore <file> or git checkout – <file>

C:\repo [branch1]> echo "foo" >> file1.txt

C:\repo [branch1 +0 ~1 -0 !]> git status
On branch branch1
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   file1.txt

no changes added to commit (use "git add" and/or "git commit -a")

C:\repo [branch1 +0 ~1 -0 !]> git restore file1.txt

C:\repo [branch1]> git status
On branch branch1
nothing to commit, working tree clean

Further reading

Git - Documentation
GitHub - dahlbyk/posh-git: A PowerShell environment for Git
A PowerShell environment for Git. Contribute to dahlbyk/posh-git development by creating an account on GitHub.