Three Git commands that all "undo" things, and picking the wrong one on a shared branch is how you ruin a teammate's afternoon. Here's how to keep them straight.
The three commands sound like synonyms. They are not. Reach for the wrong one and you either lose work or rewrite history that other people have already pulled. I've watched both happen in the same week, so let's get the distinctions clean before you type anything with --hard in it.
The short version: revert undoes a commit by making a new commit. reset moves your branch pointer backwards. restore throws away changes in files. That's the whole map. The rest is knowing which situation you're in.
Every one of these commands is really just moving stuff between three places, and if you hold that picture in your head the flags stop being cryptic.
git add puts things here.Work flows working tree → index → HEAD when you save. These three commands move it the other direction, and they each stop at a different layer. Keep that in mind and the flags below read like plain English.
git revert <commit> creates a new commit that is the mathematical inverse of the one you name. If a commit added a line, the revert removes it. Nothing gets erased, the history stays intact, and everyone who already pulled the bad commit just gets a follow-up commit that cancels it out.
git revert a1b2c3d # undo one commit, opens an editor for the message
git revert HEAD # undo the most recent commit
git revert --no-commit a1b2c3d b4c5d6e # stage several undos, commit together
This is the command for anything already pushed. It's non-destructive and it plays fair with your teammates. The only downside people complain about is the extra commit in the log, which is a strange thing to be upset about when the alternative is rewriting shared history.
git reset drags your branch pointer to a different commit. Where it stops among those three layers depends on the flag:
git reset --soft HEAD~1 # undo commit, keep changes staged in the index
git reset --mixed HEAD~1 # undo commit, keep changes in working tree (the default)
git reset --hard HEAD~1 # undo commit, discard changes entirely
--soft is my go-to for "I committed too early." It rewinds the commit but leaves everything staged, ready to recommit properly. --mixed, the default, unstages too, so you're back to edited-but-not-added. --hard also wipes the working tree, which is exactly as final as it sounds.
Because reset rewrites where the branch points, it's a local-only tool. Run it on commits you've pushed and your branch diverges from the remote, so your next push gets rejected and the "fix" is a force-push that stomps on whatever your colleagues built on top. Don't. Reset is for cleaning up your own laundry before anyone sees it.
git restore is the newer command carved out of the overloaded git checkout, and it only deals with file contents. No commits, no branch pointers.
git restore config.yml # discard working-tree edits to one file
git restore . # discard all unstaged edits (careful)
git restore --staged config.yml # unstage, keeping the edits
git restore --source=HEAD~2 app.py # pull a file's older version back
If you've ever fumbled git checkout -- file and worried you were about to switch branches instead, restore is the fix. It does one job. Discarding working-tree changes and unstaging files used to share a command with branch switching, which confused everyone; now they're split (switch handles branches, restore handles files).
| Situation | Command |
|---|---|
| Bad commit already pushed / on a shared branch | git revert |
| Local commits you haven't pushed, want to reshape | git reset (usually --soft or --mixed) |
| Uncommitted file edits you want gone | git restore |
| Staged something by mistake | git restore --staged |
| Need an old version of one file | git restore --source=<commit> |
Read it top to bottom. The first question is always "is it pushed?" If yes, you're in revert territory and the conversation is over. If it's local and about commits, reset. If it's about files rather than commits, restore.
Here's the part that makes reset --hard survivable on your own branch: Git rarely deletes commits immediately. It just stops pointing at them. git reflog shows every position HEAD has held, including the commit you thought you nuked.
git reflog # find the commit you lost, note its hash
git reset --hard a1b2c3d # or: git checkout a1b2c3d
I've recovered from a panicked --hard more than once this way. It's not infinite, unreachable commits eventually get garbage-collected, but you've usually got a couple of weeks. Knowing reflog exists is the difference between "oh no" and "give me thirty seconds."
Default to revert the instant anything has left your machine. It costs you one extra commit and buys you a history nobody has to un-tangle later. Save reset for tidying local work before you push, lean on --soft far more than you think, and treat reset --hard on a shared branch as something you simply do not do. Use restore for files, because that's what it's for and it says what it means. And whenever the panic rises, run git status and git reflog first. If you want the broader map of Git messing with your day, our Git troubleshooting guide covers the rest.
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
One bundles your whole toolchain, the other lets you build anything from parts. The right pick depends on how much glue you want to own.
GitHub Actions OIDC gets all the attention, but GitLab, Buildkite, and CircleCI issue the same signed tokens. Here's how to trust them without opening a hole.
Explore more articles in this category
The IaC landscape fractured after the Terraform license change. This is the map to what each tool is actually best at, and how to choose without regret.
Terragrunt keeps large Terraform setups DRY and orchestrated, but small teams often pay its learning curve for little return.
A practical comparison of Kubernetes-native continuous reconciliation against the classic CLI-driven, state-file IaC model for platform teams.
Evergreen posts worth revisiting.