These all "undo" something, but in very different ways and with very different safety profiles on shared history:
| Command | What it does | Rewrites history? | Safe on pushed/shared commits? |
|---|---|---|---|
git reset --soft <commit> | Moves the branch pointer, keeps all changes staged | Yes | No |
git reset --mixed <commit> (default) | Moves the branch pointer, keeps changes in the working tree, unstages them | Yes | No |
git reset --hard <commit> | Moves the branch pointer, discards later commits and uncommitted changes entirely | Yes | No |
git revert <commit> | Creates a new commit that undoes an earlier one's changes | No — adds to history instead | Yes |
git restore <file> | Discards uncommitted changes to a file in the working tree, back to the last commit (or a given commit with --source) | N/A — doesn't touch commit history at all | N/A |
git checkout <commit> -- <file> | Restores a specific file's contents from a specific commit, without moving any branch pointer | No | N/A |
If a bad commit is already public (pushed, and possibly already pulled by someone else), git revert is almost always the right tool — it fixes the problem by adding a new, honest commit that undoes the damage, without rewriting anything anyone else has already based work on. git reset --hard is fine, even useful, on commits that exist only locally and haven't been shared with anyone.
git restore (a newer, more explicit split-out of what git checkout used to do for both files and branches in one overloaded command) is worth preferring for file-level undo specifically, since git checkout alone is ambiguous about whether you mean "switch branches" or "discard changes to this file" — git restore can only mean the latter.