git reset and git revert both undo changes, but git revert is the only safe way to undo changes that have already been pushed to a shared repository.
Let’s see git revert in action. Imagine you have a Git history that looks like this:
* 7a1b2c3 (HEAD -> main) Add feature X
* 4d5e6f7 Implement bug fix Y
* 8g9h0i1 Initial commit
You realize that "Add feature X" (commit 7a1b2c3) introduced a bug. You want to undo it.
Instead of rewriting history with git reset, you use git revert:
git revert 7a1b2c3
Git will create a new commit that undoes the changes introduced by 7a1b2c3. The history now looks like this:
* a1b2c3d (HEAD -> main) Revert "Add feature X"
* 7a1b2c3 Add feature X
* 4d5e6f7 Implement bug fix Y
* 8g9h0i1 Initial commit
This new commit, a1b2c3d, contains the inverse of the changes from 7a1b2c3. If 7a1b2c3 added a line of code, a1b2c3d will remove it. If it deleted a file, a1b2c3d will add it back.
The key difference is that git revert adds to the history, preserving the original commit. This is crucial when collaborating because it doesn’t alter commits that others might have already pulled.
The problem git reset solves is that it rewrites history. If you were to use git reset --hard 4d5e6f7 to undo "Add feature X", your history would become:
* 4d5e6f7 (HEAD -> main) Implement bug fix Y
* 8g9h0i1 Initial commit
Commit 7a1b2c3 (and any subsequent commits) would be gone from your current branch’s history. If you had already pushed 7a1b2c3 to a remote repository, and someone else had pulled it, then running git push after your git reset would cause a conflict. They would have 7a1b2c3 in their history, and you wouldn’t. To reconcile this, you’d have to perform a "force push" (git push --force), which is generally discouraged because it overwrites the remote history and can cause significant problems for your collaborators. They might lose their own work if they try to merge their changes with your rewritten history.
So, the mental model is this: git revert is like writing a new page in a book that says "Undo what was written on page X." The original page X is still there, but there’s a clear instruction to disregard its content. git reset is like ripping out page X entirely and then re-pasting the pages together. The history is cleaner, but the original information is lost, and if anyone else was reading the book based on the original page X, they’ll be confused.
You control git revert with the commit hash of the change you want to undo. You can also revert a range of commits or even revert a revert. For example, to revert the last commit on your current branch:
git revert HEAD
This command creates a new commit that undoes the changes from the most recent commit. Git will open your configured editor to allow you to modify the commit message for the revert commit. The default message is usually informative, like "Revert 'Commit message of the commit being reverted'".
The most surprising true thing about git revert is that it’s not about removing bad commits from history; it’s about adding a new commit that negates the effects of a previous commit. This distinction is critical for maintaining a stable, shared history. While git reset might seem more appealing for a "cleaner" history, its destructive nature makes it unsuitable for anything that has been shared.
If you run git revert and there are conflicts (meaning the code you’re trying to revert has been modified by subsequent commits), Git will stop and ask you to resolve them manually, similar to a merge conflict. Once resolved, you’ll git add the resolved files and then git revert --continue to finalize the revert commit.
The next concept you’ll likely encounter is how to manage merge conflicts when reverting changes that have been modified.