Programming and IT

How to undo a commit

The question "how do I undo a commit" actually hides four different ones: fix the last commit, take it back and redo it, erase several, or cancel an old commit in the middle of history. The answer depends above all on one thing — whether anyone besides you has seen that commit.

Updated
In this article

First question: has the commit been pushed?#

The answer determines everything else, so start here:

$ git status -sb
## main...origin/main [ahead 1]

ahead 1 means you have one commit more than the server, and nobody has seen it. You can rewrite history freely. If there is no such note, the commit has already gone out — you must not rewrite it, because your teammates will keep the old version and the next merge will show the changes twice.

Hence a simple rule: fix private commits by rewriting, shared ones with a reverting commit. The methods below are laid out along that line.

Method 1: fix the last commit#

You forgot a file or made a typo in the message — do not remove the commit, extend it:

$ git add tests/test_rates.py
$ git commit --amend
[main 4e77b02] Add exchange rates with a test
 Date: Mon Sep 14 12:31:02 2026 +0300
 2 files changed, 38 insertions(+)

An editor opens with the previous message — change it or leave it. To change only the set of files without touching the text, add --no-edit.

It is important to understand what happens: the old commit is not modified; a new one with a different hash is created in its place. For a pushed commit this is the same history rewriting as a rebase, with all the restrictions described in the article on git rebase.

Method 2: take the commit back and keep the work#

The commit was made too early or with the wrong set of files:

$ git log --oneline -2
4e77b02 (HEAD -> main) Add exchange rates with a test
a03f1d9 Move settings to env
$ git reset --soft HEAD~1
$ git status -sb
## main...origin/main
M  rates.py
A  tests/test_rates.py

The commit is gone, the changes are intact and staged — you can regroup them: commit part now and part later. Without the flag (git reset HEAD~1) the changes stay in the files but are unstaged.

The --hard flag is not needed in this scenario and is dangerous: it does not just remove the commit, it erases everything that is not in other commits, including edits you had not added yet. All three modes are explained in the article on git reset.

Method 3: delete the last commit entirely#

If you really do not need the commit's contents:

$ git reset --hard HEAD~1
HEAD is now at a03f1d9 Move settings to env

Before running this, always look at git status -s: everything in that output will disappear. The deleted commit itself lives in the repository for several more weeks and can be brought back (see the last section), but uncommitted edits cannot.

Several commits are removed the same way: HEAD~3 removes the last three.

Method 4: a reverting commit for a shared branch#

git revert rewrites nothing. It reads the commit you name, works out the opposite changes and makes a new commit from them on top:

$ git revert 77d0e1b
[main 91ac4f0] Revert "Round currency values"
 1 file changed, 7 insertions(+), 7 deletions(-)
$ git log --oneline -2
91ac4f0 (HEAD -> main) Revert "Round currency values"
77d0e1b Round currency values

Both commits stay in history: you can see what was done and that it was changed back. Such a commit goes out with an ordinary git push and gets in nobody's way — which is why it is the only safe method for code that has been pushed.

Useful variations:

git revert --no-commit 77d0e1b   # prepare the undo, make the commit yourself
git revert HEAD~3..HEAD          # revert a range of commits
git revert -m 1 5c1a9f0          # revert a merge: 1 is the line that stays

If the changes you are reverting have since been built on by later edits, Git stops on a conflict. You resolve it in the usual way, described in how to resolve a merge conflict; then git revert --continue finishes the job and git revert --abort gives it up.

What not to do#

Force-push after rewriting a shared branch. git push --force replaces the server branch with yours and silently destroys commits that appeared there after your last fetch. If the rewrite has been agreed with the team, push like this:

git push --force-with-lease

The command refuses to work if the tip on the server is not the one you saw. This is not complete protection (another terminal of yours might have fetched in the meantime), but it catches the most common case — a teammate who pushed work five minutes ago. More in the article on git push.

Undoing a commit by editing files by hand. Putting the files back by hand and committing works, but it is murky: history gets a commit with no explanation. git revert does the same thing more precisely and labels it for you.

If you undid too much#

Commits can be recovered until the garbage collector takes them:

$ git reflog
a03f1d9 HEAD@{0}: reset: moving to HEAD~1
4e77b02 HEAD@{1}: commit: Add exchange rates with a test
a03f1d9 HEAD@{2}: commit: Move settings to env
$ git switch -c rescue 4e77b02
Switched to a new branch 'rescue'

The rescue branch now points at the "deleted" commit, and its contents are available. From there you can bring it back or take individual files.

This trick does not work for uncommitted changes: the reflog only remembers commits. That is exactly why, before any risky operation, the cheapest insurance is a draft commit or stashing your edits.

Step-by-step plan

  1. Check whether the commit was pushedgit status -sb: ahead means the commit is yours alone and can be rewritten.
  2. A small fix to the last commitgit add what is missing and git commit --amend, with --no-edit if needed.
  3. Take the commit back, keep the workgit reset --soft HEAD~1 and regroup the changes.
  4. Undo something already pushedgit revert <hash>; on a conflict, resolve it and run git revert --continue.
  5. Check the resultgit log --oneline and git status: the expected tip and the expected contents.
  6. If you undid too muchgit reflog, find the commit, pin it with a branch via git switch -c.

Start learning this in your own space

The plan goes into your repository: tick off stages, keep notes — the change history shows how far you have come.

Start the plan

Check yourself

1.A commit has been pushed to a shared branch. Which command undoes it without rewriting history?

2.You forgot to add one file to the last, unpushed commit. What is the shortest route?

3.What happens to the commit when you run git commit --amend?

4.You reset the branch with git reset --hard HEAD~1 and it turns out you need that commit. What helps?

Sources

Was this helpful?

More articles

Programming and IT git commit A commit is a saved state of your project with an author, a timestamp and a message. git commit takes what you selected beforehand with git add and writes it into history as one indivisible piece. Programming and IT git merge Merging brings work from one branch into another. git merge takes the branch you name and joins its history with the current one — sometimes by just moving a pointer, sometimes by creating a separate commit, and sometimes by stopping and asking for help. Programming and IT Git branches A Git branch is a movable pointer to a commit, not a copy of your files. That is why creating one costs nothing, and the git branch command is what you use to list, create, rename and delete branches. Programming and IT git push Until you run git push, your commits exist only on your own disk. The command sends them to a remote repository and moves the branch pointer there — which is also why it is the command that most often refuses, when someone else got their work in first. Programming and IT git rebase git rebase moves the commits of your branch as if you had started work not from an old state but from the current one. History becomes linear, but the commits are created anew — with new hashes. That leads to the main rule: you can only rebase what nobody else is using. Programming and IT git pull git pull brings other people's commits from a remote repository and immediately merges them into your current branch. It is two operations in a row, and almost every problem with pull comes from the second one — how it joins the two histories.

More solutions