datarekha
Git June 7, 2026

Undoing things in Git: reset, revert, restore, and reflog

A practical guide to undo in git: when to reach for reset revert reflog restore or amend, and how to recover commits you thought were gone forever.

12 min read · by datarekha · gitundoresetrevertreflog

You typed git reset --hard a second before realising the branch you meant to reset was not the one in your terminal. The working tree is clean. The commits are gone. Your stomach drops.

This moment — or something close to it — visits almost every developer who uses Git seriously. The good news is that Git almost certainly still has your work. The better news is that once you understand the small set of undo commands and what each one actually touches, the panic becomes manageable and eventually rare. This post is that map.

If you are new to Git, the install and config guide and the branches overview are useful starting points. The commands below assume a repository is already set up.

The three layers Git tracks

Before diving into commands, it helps to name the three places Git can hold your changes:

  • Working directory — files on disk that you can see and edit.
  • Staging area (index) — changes you have marked with git add, ready for the next commit.
  • Commit history (HEAD) — the chain of snapshots that makes up your branch.

Every undo command targets one or more of these layers. Knowing which layer your problem lives in points you straight to the right tool. See the Git glossary for definitions of HEAD, index, and ref.

git restore: the everyday discard

git restore was separated from git checkout in Git 2.23 to give each operation a clear, unambiguous name. It handles two common cleanup tasks.

Discard uncommitted working-directory changes:

git restore path/to/file.txt

This throws away every edit to file.txt since the last commit. The file reverts to the HEAD version.

Unstage a file (undo git add):

git restore --staged path/to/file.txt

The changes stay in your working directory — they are just removed from the staging area. Nothing is deleted.

You can also combine both flags to unstage and discard in one step:

git restore --staged --worktree path/to/file.txt

git reset: rewriting where HEAD points

git reset moves the branch pointer (HEAD) to a different commit. Where it leaves the staging area and working directory depends on the flag you pass. This is the most nuanced undo tool in the kit, and it comes in three modes.

--soft: move HEAD, keep everything staged

git reset --soft HEAD~1

HEAD moves one commit back. The changes from that commit land in the staging area, ready for you to recommit. Nothing in the working directory changes. Useful when you want to redo a commit message or squash the last two commits together.

--mixed (the default): move HEAD and unstage

git reset HEAD~1
# equivalent to:
git reset --mixed HEAD~1

HEAD moves back. The staging area is cleared. The changes sit in your working directory as untracked modifications. This is the most forgiving mode — nothing is deleted; you just get everything back as uncommitted edits.

--hard: move HEAD, wipe staging and working directory

git reset --hard HEAD~1

HEAD moves back, the index is cleared, and every uncommitted change in the working directory is discarded. From Git’s point of view your working tree is identical to the target commit.

The diagram below shows which layers each mode touches.

git reset: what each mode touchesHEAD (commit history)moved by ALL three modesStaging area (index)cleared by —mixed and —hardWorking directorywiped only by —hard—soft✓ movedunchangedunchanged—mixed✓ moved✓ clearedunchanged—hard✓ moved✓ cleared✓ wiped
Each row shows how far the reset reaches. Only —hard touches your working directory files.

git revert: the safe undo for shared history

When a commit has already been pushed to a shared branch, git reset is the wrong tool. Rewriting history that other people have pulled causes diverged branches and force-push drama. git revert takes a different approach: it reads a commit, calculates the inverse changes, and creates a new commit that cancels those changes out.

git revert abc1234

This opens your editor for a commit message (pre-filled with “Revert …”), saves a new commit, and leaves the history intact. The bad commit is still there in the log — it is just followed by a commit that undoes it. Remote history is preserved, so collaborators can pull normally.

You can revert multiple commits by passing a range, or suppress the editor with --no-edit:

git revert --no-edit HEAD~3..HEAD

The key insight: git revert is additive. git reset is reductive. On public branches, always add; never rewrite.

git commit --amend: fix the last commit before it leaves

If you just committed and immediately noticed a typo in the message, or forgot to stage one file, --amend lets you fold the fix into that commit without creating a second one:

# Stage the forgotten file, then:
git commit --amend
# Or change only the message:
git commit --amend -m "correct message here"

Amend rewrites the last commit (it creates a new SHA). Like reset, this is only safe before the commit is pushed. If the commit is already on a shared remote, amending and force-pushing will cause the same diverged-history problems described above.

git reflog: the safety net under everything

The reflog is Git’s internal diary of every position HEAD has occupied. Every commit, reset, rebase, cherry-pick, and checkout appends an entry. It is local to your machine and kept for roughly 90 days by default.

git reflog

Output looks like this:

e7f3a12 HEAD@{0}: reset: moving to HEAD~2
9c8b1f0 HEAD@{1}: commit: add pagination to user list
4a2d345 HEAD@{2}: commit: wire up search endpoint

If you ran git reset --hard two commits too far, HEAD@{1} is the commit you want. Restore it with:

git reset --hard HEAD@{1}

Or recover it as a new branch without touching your current one:

git branch recovery-branch HEAD@{1}

You can filter the reflog to a specific branch:

git reflog show feature/my-branch

Decision guide: which tool for which situation

The diagram below walks you through the most common undo scenarios.

Choosing the right undo commandWhat do you want to undo?Start hereUncommitted file edit(working directory)A staged add(index only)A commit(local or pushed?)git restore file.txtdiscards working editgit restore —stagedremoves from indexLocal only?(not yet pushed)Pushed / shared?(others have it)git reset / amendrewrite local history safelygit revertadds inverse commit
Follow the branch matching your situation. The rule of thumb: if the commit is already shared, always revert rather than reset.

Cleaning untracked files: git clean

git restore and git reset only handle tracked files. Files that have never been added to the index are invisible to both commands. For those, use git clean.

Always run a dry run first:

git clean -n

This lists what would be deleted without deleting anything. When you are satisfied, run:

git clean -f

Add -d to also remove untracked directories, and -x to also remove files ignored by .gitignore (useful for clearing build artefacts).

Putting it together: a realistic recovery sequence

You ran git reset --hard HEAD~3 on the wrong branch and lost three commits. Here is the full recovery:

# Step 1: find the lost commits
git reflog

# Output shows something like:
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# 9x8y7z6 HEAD@{1}: commit: the work you want back

# Step 2: restore HEAD to that commit
git reset --hard HEAD@{1}

# Or safely branch off it first:
git branch rescued HEAD@{1}

The reflog entries are timestamped, so if there are many you can filter by time:

git reflog --since="2 hours ago"

For a broader look at your repository and branch structure, the Git overview is a useful reference.

Frequently asked questions

Can I undo a git push?

You can, but you should not on shared branches. A force push (git push --force-with-lease) will overwrite the remote with your local state, but anyone who has already pulled the original commits will have a diverged history. On a branch only you use, it is acceptable. On main or any team branch, use git revert instead.

How long does the reflog keep entries?

Git’s default is 90 days for reachable commits and 30 days for unreachable ones (those no longer pointed to by any branch or tag). These are controlled by gc.reflogExpire and gc.reflogExpireUnreachable in your git config. On a healthy machine you are very unlikely to hit either limit before you notice a problem.

What is the difference between git reset HEAD file and git restore --staged file?

They produce the same result. git restore --staged is the modern, purpose-built form introduced in Git 2.23. The older git reset HEAD file still works but the intent is less obvious from the syntax. New scripts and muscle memory should prefer git restore --staged.

I amended a commit that was already pushed. What now?

You have rewritten history that the remote already has. A normal git push will be rejected. Your options are: force push with git push --force-with-lease if the branch is yours alone, or accept the situation and create a new commit that corrects the mistake (the git revert approach). On any shared branch, the new commit is the right answer.

Skip to content