Git Reset — Hard Reset on Main Lost 8 Developers 2 Hours
Eight developers saw 'Your branch and origin/main have diverged' after a git reset --hard and force push.
- --soft: HEAD moves back, changes stay staged — ready to recommit
- --mixed (default): HEAD moves back, changes unstaged — in working directory only
- --hard: HEAD moves back, changes discarded — working directory matches target commit
Git reset moves the branch pointer backwards in history. The three modes answer one question: what happens to the changes in the commits you're 'undoing'? Hard: throw them away. Soft: keep them staged. Mixed (the default): unstage them but keep the files. Hard is permanent. Soft and mixed are undo buttons.
git reset moves the current branch pointer to a specified commit. The three modes (--soft, --mixed, --hard) control what happens to your working directory and staging index after the move. Only --hard discards changes. The other two are non-destructive.
The distinction between reset and revert is critical: reset rewrites history by moving the branch pointer backward. Revert adds a new commit that undoes a previous change. Reset is for local cleanup. Revert is for shared branches where rewriting history would break teammates.
Common misconceptions: that all reset modes lose work (only --hard does), that reset is the same as revert (reset rewrites history, revert preserves it), and that --hard is unrecoverable (committed work is recoverable via reflog for 30 days, but uncommitted working directory changes are not).
The Three Modes: Hard, Soft, Mixed
All three modes move HEAD (and the current branch pointer) to the specified commit. The difference is entirely in what happens to the working directory and the staging index.
--soft: HEAD moves back. Working directory untouched. Staging index untouched. The changes from the undone commits are staged and ready to recommit. Use this for 'I committed too early' or 'I want to rewrite the last N commits into one'.
--mixed (default): HEAD moves back. Working directory untouched. Staging index cleared. Changes appear as unstaged modifications. Use this for 'I committed the wrong things' or 'I need to regroup and re-add selectively'.
--hard: HEAD moves back. Working directory changed to match the target commit. Staging index cleared. Any uncommitted changes are gone. Use this when you genuinely want to throw away work. This is the only mode that can lose data.
- --soft: changes stay staged — ready to recommit immediately
- --mixed: changes unstaged — in working directory, need to re-add selectively
- --hard: changes discarded — working directory matches the target commit
- Only --hard risks data loss. --soft and --mixed are non-destructive undo buttons.
git reset vs git revert: Which to Use
This is the question that trips up the most people in interviews and in practice. The key distinction: reset rewrites history, revert adds to history.
git reset moves the branch pointer backward. The commits you reset past effectively disappear from the branch. This is fine on your local feature branch. On a shared branch, it rewrites public history and requires a force push — which breaks teammates' local copies.
git revert <hash> creates a new commit that undoes the changes of the specified commit. History is preserved. The original commit is still there. You push normally. This is what you use on main, develop, or any shared branch.
Rule of thumb: reset for local cleanup on your own branches. Revert for undoing things on shared branches.
- Reset: moves branch pointer backward, commits disappear from branch
- Revert: creates new commit that undoes previous change, history preserved
- Reset on shared branch: requires force-push, breaks teammates
- Revert on shared branch: normal push, no breakage, clean fast-forward for teammates
git branch -r and check if the branch exists on the remote. If it does, someone else may have pulled it. Use revert. If it does not, reset is safe. When in doubt, use revert — it is never wrong, even on local branches.Recovering from a Bad --hard Reset
git reset --hard is the only mode that can lose data. But the data loss is not as total as it sounds — it depends on whether the changes were committed or not.
Committed changes are recoverable for 30 days via git reflog. Every time HEAD moves, Git records the movement in the reflog. When you reset --hard past a commit, that commit is still in the reflog. You can find it, create a branch pointing to it, and your work is back.
Uncommitted working directory changes are NOT recoverable via reflog. If you had modified files that were not staged or committed, and you ran --hard, those changes are gone. The only recovery path is editor local history (VS Code, IntelliJ) or filesystem-level recovery tools.
The safety habit: always run git status before --hard. If the working tree is not clean, stash first. If you are resetting past commits, check git reflog to confirm the commit hash is recorded before proceeding.
- Committed work: recoverable via git reflog for 30 days
- Uncommitted work: NOT in reflog — permanently lost if not staged
- Staged but uncommitted: may be recoverable via git fsck --unreachable
- Prevention: always git status before --hard. If dirty, stash first.
git reset --hard on main + force push: 8 Developers Lose 2 Hours
git reset --hard HEAD~5 to undo the last 5 commits (including the bad one and 4 good ones).
3. They force-pushed: git push --force origin main.
4. Eight teammates had already pulled those 5 commits and had feature branches based on them.
5. On their next fetch, origin/main pointed to the reset state (before the 5 commits).
6. Their feature branches were based on commits that no longer existed on the remote.
7. Git saw the branches as diverged — old commits vs new (reset) commits had different histories.
8. One developer merged the diverged branches, creating a merge commit with duplicate changes.
9. The CI pipeline built from the force-pushed main without the 5 commits.git revert on the specific bad commit: git revert <bad-commit-hash>.
3. Pushed the revert commit normally: git push origin main. No force-push needed.
4. All 8 developers ran git pull to get the revert commit — clean fast-forward, no divergence.
5. Team rule: never reset or force-push main. Use git revert for undoing on shared branches.
6. Added branch protection on GitHub to prevent force-pushes to main.- git reset on a shared branch rewrites public history. Every teammate who has pulled the old commits must recover manually.
- git revert is always the correct tool for undoing changes on shared branches. It adds a new commit without rewriting history.
- Branch protection rules on GitHub/GitLab prevent force-pushes to main and develop. Configure them immediately.
- If you accidentally force-push to main, announce it immediately and coordinate recovery before anyone merges the diverged state.
git fetch origin to get the reset remote state.
4. Hard-reset: git reset --hard origin/main to align with the remote.
5. If you had local commits on top of the old main: cherry-pick them onto the new base.git fsck --unreachable | grep blob pull. Some had local may status before --hard.Key takeaways
Interview Questions on This Topic
Frequently Asked Questions
That's Git. Mark it forged?
3 min read · try the examples if you haven't