--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
✦ Definition~90s read
What is Git Reset?
Git reset is a command that moves the current branch pointer backward in the commit graph, effectively rewriting history by discarding commits from your branch. Unlike git revert, which creates a new commit that undoes changes while preserving history, git reset surgically removes commits from your branch's timeline.
★
Git reset moves the branch pointer backwards in history.
This makes it dangerous on shared branches because anyone who has pulled those commits will have divergent histories. The command operates in three modes: --soft (keeps all changes staged), --mixed (keeps changes unstaged, default), and --hard (discards all changes entirely).
A --hard reset is the nuclear option — it wipes both commits and working directory changes, which is why losing 8 developers for 2 hours happens when someone runs it on main. Recovery is possible if you have the commit hash from reflog, but only if no garbage collection has run.
For shared branches, always use git revert instead; for local work-in-progress, git reset is fine. The rule of thumb: never reset a commit that has been pushed to a shared branch unless you're prepared to force-push and coordinate every teammate to rebase their work.
Plain-English First
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).
What Is Git Reset — The Fundamentals
git reset moves the current branch pointer backward in history. It's the primary tool for undoing LOCAL commits. The three modes control what happens to the changes from the undone commits.
The three modes and their effects:
Mode
HEAD
Staging Area
Working Directory
--soft
Moved back
Unchanged
Unchanged
--mixed (default)
Moved back
Reset
Unchanged
--hard
Moved back
Reset
Overwritten
git reset --soft HEAD~1 — Undo the last commit. Changes remain staged. Recommit with a fix.
git reset HEAD~1 — Undo the last commit. Changes unstaged but kept. Restage selectively.
git reset --hard HEAD~1 — Undo the last commit AND discard changes. Permanent loss risk.
Critical distinction: reset vs revert - reset rewrites history by removing commits. Safe ONLY for local, unpushed commits. - revert creates new commits that undo old ones. Safe for ALL branches.
When to use each: - git reset = local undo (the commit hasn't been seen by anyone else) - git revert = shared undo (the commit was pushed) - The one exception: git revert is always safe, even for local commits. If you're unsure, use revert.
01_reset_basics.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Create3 commits to experiment with
for i in 123; do echo 'Line '$i > file.txt && git add . && git commit -m 'Commit '$i; done
git log --oneline # shows 3 commits
# SOFT reset: undo last commit, keep changes staged
git reset --soft HEAD~1
git status # file.txt is STAGED
# Recommit with a better message
git commit -m 'Better message for commit 3'
# MIXED reset: undo last commit, unstage changes
git reset HEAD~1
git status # file.txt is MODIFIED but not staged
# Stage only some changes and commit
git add -p # selective stage
git commit -m 'Selected changes'
# HARD reset: undo last commit ANDdiscard (DANGEROUS)
git reset --hard HEAD~1
git log --oneline # commit is gone
# RECOVER from hard reset via reflog
git reflog | head -5 # find the SHA before reset
git reset --hard HEAD@{1} # restore
Output
c3d4e5f Commit 3
b2c3d4e Commit 2
a1b2c3d Commit 1
[main d4e5f6g] Better message for commit 3
c3d4e5f Commit 3
b2c3d4e Commit 2
a1b2c3d Commit 1
b2c3d4e Commit 2
a1b2c3d Commit 1
b2c3d4e Commit 2
a1b2c3d Commit 1
⚠ Never reset --hard Pushed Commits on Shared Branches
If a commit has been pushed and other people have pulled it, git reset --hard and git push --force will destroy their local history. Always use git revert for pushed commits. reset --hard is only safe for commits that exist ONLY on your local machine.
📊 Production Insight
The safest reset workflow in CI/CD: git reset --soft to undo commits while keeping changes staged, then git commit with a corrected message or scope. Never use --hard in any automated script that touches a shared branch — use git revert instead. The three-mode design gives you granular control: soft for message fixes, mixed for commit splitting, hard for throwaway branches.
🎯 Key Takeaway
Reset moves the branch pointer backward. Three modes: soft (keep staged), mixed (unstage but keep files), hard (discard everything). Reset rewrites history — only safe for local commits. For pushed commits, use git revert. Hard reset is recoverable via reflog for 90 days.
thecodeforge.io
Git Reset
What Git Reset Actually Does to Your Commit Graph
Git reset is a command that moves the current branch pointer to a specified commit and optionally updates the index (staging area) and working tree. It rewrites history by changing where your branch points — it does not delete commits immediately, but orphaned commits become eligible for garbage collection. The three modes (--soft, --mixed, --hard) control how far the change propagates: --soft only moves HEAD, --mixed resets the index, --hard resets both index and working tree.
In practice, --hard is the most destructive because it discards uncommitted changes and resets tracked files to the target commit's state. Unlike revert, which creates a new commit that undoes changes, reset actually moves the branch pointer backward. This means any commits after the target become unreachable from that branch — they still exist in the object store for ~30 days (default gc expiry) but are invisible to normal git log.
The primary use case is cleaning up local branches before pushing, or undoing commits that haven't been shared. On shared branches like main, reset is dangerous because other developers' histories diverge. The moment you force-push a reset, every collaborator must rebase or re-clone — a single --hard reset on main can cost a team hours of recovery work.
⚠ Reset ≠ Revert
Reset rewrites history by moving the branch pointer; revert creates a new commit that undoes changes. Never reset a branch others have pulled from.
📊 Production Insight
A developer runs git reset --hard HEAD~1 on main to remove a bad commit, then force-pushes. All other developers' local main branches now point to a commit that no longer exists in the remote. Their next git pull fails with 'rejected — non-fast-forward'. The team spends 2 hours rebasing each local branch and re-syncing CI/CD pipelines.
🎯 Key Takeaway
Never use --hard on a branch that has been pushed and pulled by others.
Always use git revert to undo public commits — it preserves history and avoids force-push chaos.
If you must reset a shared branch, coordinate a team-wide freeze and provide a recovery script.
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.
io/thecodeforge/git/ResetModes.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# io.thecodeforge — GitResetModes
# ─────────────────────────────────────────────────────────────
# Current state: 3 commits ahead of where we want to be
# ─────────────────────────────────────────────────────────────
git log --oneline
# d4f8b3c (HEAD) Add unit tests
# 9c3e8a2 Fixnull check
# 7b2d4f1 Add backoff logic
# a3f9c2e (origin/main) InitialPaymentRetryService
# ─────────────────────────────────────────────────────────────
# --soft: undo commits, keep changes STAGED
# ─────────────────────────────────────────────────────────────
git reset --soft HEAD~3
git status
# Changes to be committed: all 3 commits' changes are staged
# Usecase: squash 3 commits into 1 clean commit
git commit -m "feat(payment): add retry logic with exponential backoff and unit tests"
# ─────────────────────────────────────────────────────────────
# --mixed (default): undo commits, keep changes UNSTAGED
# ─────────────────────────────────────────────────────────────
git reset HEAD~3
# or: git reset --mixed HEAD~3
git status
# Changes not staged for commit: all 3 commits' changes in working dir
# Usecase: regroup changes before re-adding selectively
git add src/main/java/io/thecodeforge/payment/PaymentRetryService.java
git commit -m "feat(payment): add retry logic with exponential backoff"
# ─────────────────────────────────────────────────────────────
# --hard: undo commits, DISCARD changes entirely
# ─────────────────────────────────────────────────────────────
git reset --hard HEAD~3
git status
# nothing to commit, working tree clean
# WARNING: the changes from those 3 commits are gone
# ─────────────────────────────────────────────────────────────
# Reset to a specific commit hash
# ─────────────────────────────────────────────────────────────
git reset --soft a3f9c2e
git reset --hard a3f9c2e
# ─────────────────────────────────────────────────────────────
# Reset a single file (unstage it from the index)
# ─────────────────────────────────────────────────────────────
git reset HEAD -- src/main/java/io/thecodeforge/payment/PaymentService.java
# This unstages the file but does NOT change the working directory content.
new file: src/test/java/io/thecodeforge/payment/PaymentRetryServiceTest.java
# --hard result:
HEAD is now at a3f9c2e Initial PaymentRetryService
Mental Model
The Three Modes Answer One Question: What Happens to the Changes?
Soft = staged. Mixed = unstaged. Hard = gone.
--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.
📊 Production Insight
The --soft mode is the most underused production reset. When a developer makes 5 WIP commits and wants to squash them into one clean commit before a PR, they reach for interactive rebase. But --soft HEAD~5 followed by a single git commit achieves the same result in two commands with no interactive editor. The changes are staged and ready — just write a clean commit message and commit. This is faster and less error-prone than interactive rebase for simple squash operations.
🎯 Key Takeaway
Three modes, one question: what happens to the changes? --soft keeps them staged. --mixed (default) unstages them. --hard discards them. Only --hard risks data loss. Use --soft for squashing commits, --mixed for regrouping, --hard for throwing away work.
thecodeforge.io
Git Reset
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.
io/thecodeforge/git/ResetVsRevert.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# io.thecodeforge — Reset vs Revert
# ─────────────────────────────────────────────────────────────
# RESET: rewrites history (local branches only)
# ─────────────────────────────────────────────────────────────
git reset --hard HEAD~1 # Undo last commit, discard changes
git reset --soft HEAD~1 # Undo last commit, keep changes staged
# If already pushed: requires force push — DON'T do on shared branches
git push origin feature/my-branch --force-with-lease
# ─────────────────────────────────────────────────────────────
# REVERT: adds a new'undo'commit (safe for shared branches)
# ─────────────────────────────────────────────────────────────
git revert HEAD # Undo last commit
git revert a3f9c2e # Undo specific commit
git revert HEAD~3..HEAD # Undo last 3commits (creates 3 revert commits)
git revert HEAD~3..HEAD --no-commit # Stage all reverts, commit once
git push origin main # Normal push — history intact
# ─────────────────────────────────────────────────────────────
# DECISION: reset or revert?
# ─────────────────────────────────────────────────────────────
# Branch is local-only (not pushed)? -> reset is safe
# Branch is shared (main, develop)? -> revert is required
# Need to undo a specific commit? -> revert <hash>
# Need to squash local commits? -> reset --soft HEAD~N
Output
[feature/payment-retry 9f2c4a1] Revert "Add PaymentRetryService with exponential backoff"
1 file changed, 47 deletions(-)
Mental Model
Reset Rewrites History. Revert Adds to History.
Reset = erase from history. Revert = undo in history.
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
📊 Production Insight
The reset-vs-revert decision is about whether the branch is shared. If you are the only person who has seen the commits, reset is fine. If anyone else has pulled those commits, revert is required. The test: run 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.
🎯 Key Takeaway
Reset rewrites history — safe on local branches only. Revert adds to history — safe on all branches. Rule of thumb: reset for local cleanup, revert for shared branches. When in doubt, use revert — it is never wrong.
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.
io/thecodeforge/git/RecoverFromReset.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# io.thecodeforge — Recover from Bad --hard Reset
# ─────────────────────────────────────────────────────────────
# SCENARIO: You ran git reset --hard and lost committed work
# ─────────────────────────────────────────────────────────────
# Step1: Find the lost commit in reflog
git reflog
# Output:
# a3f9c2e HEAD@{0}: reset: moving to HEAD~3
# d4f8b3c HEAD@{1}: commit: Add unit tests
# 9c3e8a2 HEAD@{2}: commit: Fixnull check
# 7b2d4f1 HEAD@{3}: commit: Add backoff logic
# Step2: Recreate a branch at the lost commit
git branch recovery d4f8b3c
# Or reset forward to restore the commits:
git reset --hard d4f8b3c
# Step3: Verify the recovery
git log --oneline -3
# d4f8b3c Add unit tests
# 9c3e8a2 Fixnull check
# 7b2d4f1 Add backoff logic
# ─────────────────────────────────────────────────────────────
# SCENARIO: You ran git reset --hard and lost UNCOMMITTED work
# ─────────────────────────────────────────────────────────────
# Uncommitted changes are NOT in reflog.
# Recovery options:
# Option1: Checkif the changes were staged before reset
# Staged changes may be recoverable as orphaned blobs:
git fsck --unreachable | grep blob
# This lists blob objects that are not referenced by any commit.
# Some may be your lost file contents.
# Option2: Check editor local history
# VSCode: right-click file > LocalHistory
# IntelliJ: right-click file > LocalHistory > ShowHistory
# These may have snapshots of your uncommitted changes.
# ─────────────────────────────────────────────────────────────
# PREVENTION: Always check status before --hard
# ─────────────────────────────────────────────────────────────
git status
# If dirty: git stash first, then reset, then git stash pop
Output
# git reflog
# a3f9c2e HEAD@{0}: reset: moving to HEAD~3
# d4f8b3c HEAD@{1}: commit: Add unit tests
# 9c3e8a2 HEAD@{2}: commit: Fix null check
# 7b2d4f1 HEAD@{3}: commit: Add backoff logic
# git branch recovery d4f8b3c
# Branch 'recovery' set up to track local branch 'feature/payment-retry'.
# git log --oneline -3
# d4f8b3c Add unit tests
# 9c3e8a2 Fix null check
# 7b2d4f1 Add backoff logic
⚠ Committed Work Is Recoverable. Uncommitted Work Is Not.
git reflog saves committed work for 30 days after a --hard reset. But uncommitted working directory changes are NOT in reflog. If you run --hard with dirty files, those changes are permanently lost unless your editor has local history. Always run git status before --hard.
📊 Production Insight
The reflog safety net is the reason --hard is less dangerous than it appears — but only for committed work. The real danger is uncommitted changes. A developer modifies 3 files, does not stage or commit, runs --hard to 'start fresh', and the changes are gone. No reflog entry. No recovery path except editor local history. The discipline: never run --hard without checking git status first. If dirty, stash. If not dirty, --hard is safe because reflog has your back.
🎯 Key Takeaway
Committed work is recoverable via reflog for 30 days. Uncommitted working directory changes are NOT recoverable. Always run git status before --hard. If dirty, stash first. The reflog safety net only covers committed work.
Undo Uncommitted Changes Before They Go Live
You've got dirty files — unstaged edits, staged garbage, maybe both. Don't panic. Before you reach for git clean -fd and nuke everything, understand what you're actually killing. git checkout -- <file> restores a file to its last committed state, discarding uncommitted changes. It's destructive. No undo button. Your changes are gone. Use git diff first to see what you're about to lose. If you want to stash those changes for later, use git stash instead — it tucks them away on a stack so you can pop them back later. This is not a feature for reverting shared history; it's for cleaning your local workspace before a merge, a rebase, or a deploy. The rule: if you haven't committed it, you can't recover it unless you stash it. Production engineers keep a mental model of three states: working tree, index, and HEAD. Knowing which one is dirty saves your ass.
For files you've already staged (git add), git reset HEAD <file> unstages them without touching the working tree. That puts you back to square one — dirty working tree but clean index. Then you can decide: git checkout -- <file> to discard everything, or fix the file and re-stage. Never skip the diff. You'll thank me when you don't accidentally nuke a config file with API keys.
DiscardLocalChanges.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// io.thecodeforge — devops tutorial
// Check what's dirty before discarding
git status
# modified: src/config/database.yml
# modified: src/lib/authenticator.py
// View exact changes
git diff src/config/database.yml
# -password: "old_db_pass"
# +password: "new_db_pass"
// Discard unstaged changes for one file
git checkout -- src/config/database.yml
// Unstage a staged file, then discard
git reset HEAD src/lib/authenticator.py
git checkout -- src/lib/authenticator.py
// Verify clean state
git status
# nothing to commit, working tree clean
Output
nothing to commit, working tree clean
⚠ Production Trap:
git checkout -- . with a dot will nuke all unstaged changes in the entire repo. One typo, hours of work gone. Always target specific files.
🎯 Key Takeaway
Diff first, then discard. Use stash if you might need it later. Never checkout blind.
Revert a Shared Commit Without Rewriting History
You pushed a commit that broke production — maybe it introduced a bug, maybe it deleted a critical file. Your first instinct might be git reset --hard HEAD~1 && git push --force-with-lease. Don't. That's rewriting shared history. Everyone else who pulled that commit now has a detached HEAD and a broken merge. The correct tool is git revert. It creates a new commit that undoes the changes of the target commit. No history rewrite. No force push. No teammates getting wrecked. git revert HEAD creates a reverse commit and opens your editor for a message. You can also revert multiple commits in one shot: git revert HEAD~3..HEAD reverts the last three commits, creating three new commits in reverse order. If you want a single revert commit for multiple commits, use git revert -n HEAD~3..HEAD to stage all the changes, then commit them manually.
This is the safe hammer for commits that have been pushed to a shared branch like main or release. It leaves a clear audit trail. Anyone looking at the log sees: commit A introduced the bug, commit B reverted it. That's good for blame, good for compliance, good for your sanity. Never force-push a shared branch to undo history. That's how you get paged at 3 AM.
RevertSharedCommit.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// io.thecodeforge — devops tutorial
// Revert the last commit
git revert HEAD
# [main 5f3a2b1] Revert"Add new pricing endpoint"
# 1 file changed, 20deletions(-)
// Revert multiple commits (creates one revert per commit)
git revert HEAD~3..HEAD
# [main a1b2c3d] Revert"Fix rate limiting"
# [main e4f5g6h] Revert"Add logging middleware"
# [main i7j8k9l] Revert"Deploy to staging"
// Revert multiple commits as a single commit
git revert -n HEAD~3..HEAD
git commit -m "Revert last 3 commits that broke auth"
# [main m0n1o2p] Revert last 3 commits that broke auth
// Pushsafely (no force)
git push origin main
# To github.com:acme/payments.git
# a1b2c3d..m0n1o2p main -> main
Output
To github.com:acme/payments.git
a1b2c3d..m0n1o2p main -> main
💡Senior Shortcut:
Use git revert --no-edit to skip the editor and auto-generate a revert message. Saves time in emergency patches.
🎯 Key Takeaway
Revert on shared branches to preserve history. Reset only on local or unreleased branches.
Discard Uncommitted Changes to a File — The Surgical Approach
A single config file is corrupted. You don't want to reset the branch. You don't want to revert a commit. You just want that one file back to its last committed state. Use git restore <file> — it's the modern replacement for git checkout -- <file>. It's explicit, it's safe, and it only touches the working tree. git restore --staged <file> unstages a file without destroying your local edits. git restore --source=HEAD~1 <file> restores a file to its state from a previous commit. This is your scalpel for surgery on a dirty working tree. No muss, no fuss, no collateral damage.
But here's where juniors burn themselves:git restore . restores all files in the current directory. That's a shotgun blast. You wanted one config file, now you've undone all your local work. Always specify the path. Always run git status first to see what you're about to destroy. If you have a mix of files you want to keep and files you want to revert, git add the keepers first, then git restore . — it only touches uncommitted files that aren't staged. That's the pro move: stage what you want, restore everything else.
SurgicalFileRestore.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// io.thecodeforge — devops tutorial
// Check current state
git status
# modified: deployment/production.yml
# modified: src/handlers/checkout.py
// Stage the file you want to keep
git add src/handlers/checkout.py
// Restore the corrupted config to last commit
git restore deployment/production.yml
// Verify
git status
# modified: src/handlers/checkout.py
// Alternatively, restore from a specific commit
git restore --source=HEAD~2 src/handlers/checkout.py
# restores checkout.py to how it looked 2 commits ago
Output
modified: src/handlers/checkout.py
🔥Senior Shortcut:
Pair git restore with git stash for selective undo: stash changes to a file, restore it, then pop the stash if you want the edits back.
🎯 Key Takeaway
Restore specific files. Never restore a directory without staging what you want to keep.
Why You Should Never git reset a Shared Branch
Hard resets rewrite history. When you force push a reset branch, every teammate who pulled the old commits now has a divergent graph. Git won't merge cleanly — you'll get fork warnings, conflicts, and confused devs blaming the build.
The WHY:git reset moves the branch pointer backward. On shared branches like main or develop, other developers' local refs still point to the deleted commits. The next git pull tries to merge those orphaned commits back in, resurrecting everything you tried to erase. This is not a rollback — it's a time bomb.
Instead, use git revert to create a new commit that undoes the changes. The branch history stays linear, your team can pull without errors, and CI doesn't break. Reserve hard resets strictly for local branches nobody else has touched. If you're asking "can I reset main?" — the answer is no.
revert-vs-reset-shared.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// io.thecodeforge — devops tutorial
# Bad: rewrites shared history
- branch: main
action: reset
commits: 3
result: "❌ DevOps on-call pages you at 2am"
# Correct: append a revert commit
- branch: main
action: revert
target_commit: "abc123"
command: git revert abc123
result: "✅ Team pulls, CI passes, sleep uninterrupted"
Output
❌ Team forks on pull
✅ Single clean revert commit created
⚠ Production Trap:
If you've already reset a shared branch and force-pushed, tell your team to rebase onto the new HEAD — not merge. git pull --rebase or they'll resurrect your mess.
🎯 Key Takeaway
Never hard reset a branch that other developers have pulled. Use revert to undo shared history safely.
git reset --soft: The Commit Squash You're Looking For
Most devs know --hard obliterates work and --mixed unstages files. But --soft is the unsung hero for squashing messy commit histories before a PR. It moves the branch pointer back but leaves every change staged. Exactly what you need when you've committed ten "fix typo" messages and want one clean diff.
The WHY:git reset --soft HEAD~3 keeps all your file changes perfectly staged. You don't lose a single line of work. You just undo the commits themselves. Then one git commit -m "Implement payment module" bundles everything into a single, reviewable chunk. No interactive rebase gymnastics, no cherry-picks, no force pushes if you haven't shared the branch yet.
This is the senior dev shortcut for local branch hygiene. Work iteratively, commit often as checkpoints, then soft-reset and squash before pushing for review. Your reviewer sees one coherent change. Your git log stays readable. Clean code wins code reviews.
soft-reset-squash.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// io.thecodeforge — devops tutorial
# Before: messy local history
- commits:
- "wip broken"
- "fix import"
- "add types"
- "oops"
# Squash with soft reset
action: reset --soft HEAD~4
staged_files: 12 # all changes preserved
# Single clean commit
command: git commit -m "Implement audit logging"
result: "1 commit, clean diff, PR approved in 10 minutes"
Pair --soft with git diff --cached before committing to verify exactly what your squash captured. Saves you from accidentally bundling debug print statements.
🎯 Key Takeaway
Use git reset --soft to squash local commits into one clean PR-ready bundle without losing any changes.
Prerequisites
Before using git reset, confirm you have a solid understanding of Git's three-tree architecture: the working directory, the staging index, and the commit history. A reset modifies where HEAD points and can rewrite history, so only proceed on branches that are not shared with other team members. You should know how to check your current branch with git branch, inspect logs with git log --oneline, and verify remote tracking branches using git remote -v. Always ensure you have a clean working state—either stash or commit uncommitted changes first—since a --hard reset will permanently discard them. If you plan to reset to a remote branch, fetch the remote's latest state to avoid resetting to stale references.
No uncommitted changes detected. Branch 'feature-x' is local only.
⚠ Production Trap:
Resetting a shared branch with --hard will force-push a rewritten history, breaking every other developer's clone. Always use git revert on public branches instead.
🎯 Key Takeaway
Only reset branches that exist exclusively on your local machine.
Steps to Reset a Git Branch to a Remote Repository
To align your local branch exactly with the remote version, first fetch the latest changes from the remote repository using git fetch origin. This downloads the up-to-date refs without merging them into your local branch. Next, inspect the remote branch reference—for example, origin/main—using git log origin/main --oneline to confirm the commit you want. Execute the appropriate reset: git reset --hard origin/main discards all local commits and uncommitted changes, making your HEAD point to the same commit as the remote. For a soft reset that preserves your local changes as staged, use --soft instead. Finally, verify the result with git status and git log --oneline -5. If the remote has diverged, a reset may discard valuable work; consider git merge --ff-only if you only need a fast-forward.
reset_to_remote.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
// io.thecodeforge — devops tutorial
// 25 lines max
- name: Fetch remote latest
shell: git fetch origin
- name: Inspect remote HEAD
shell: git log origin/main --oneline -3
- name: Hard reset to remote
shell: git reset --hard origin/main
- name: Verifynew state
shell: git log --oneline -5
- name: Show working tree
shell: git status --short
Output
HEAD is now at 9a4b3c7 Deploy hotfix v1.2. Working tree clean.
🔥Safety Net:
Before a hard reset, tag your current HEAD with git tag backup-$(date +%s) so you can recover if the reset was premature.
🎯 Key Takeaway
Always fetch before resetting to ensure you're pointing to the true remote state, not a stale local cache.
● Production incidentPOST-MORTEMseverity: high
git reset --hard on main + force push: 8 Developers Lose 2 Hours
Symptom
Eight developers reported 'Your branch and origin/main have diverged' on their next git commits on top of the reset commits that were now orphaned. One developer merged the diverged branches, creating a merge commit with duplicate changes. The CI pipeline built from the force-pushed main and deployed without the 5 commits that were intended to be temporary.
Assumption
The developer assumed that resetting main and force-pushing would cleanly undo the bad deploy. They did not realize that eight teammates had already pulled the 5 commits and built feature branches on top of them. They did not announce the force-push.
Root cause
1. A bad commit was pushed to main and deployed to production, causing errors.
2. The developer ran 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.
Fix
1. Immediate: the developer force-pushed again to restore the 5 commits (found via their own reflog).
2. Instead of reset, used 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.
Key lesson
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.
Production debug guideSystematic recovery paths for accidental resets, lost work, and shared-branch resets.6 entries
Symptom · 01
'Your branch and origin/main have diverged' after a teammate reset and force-pushed main
→
Fix
1. Your local branch is based on old commits that no longer exist on the remote.
2. Do NOT merge — this creates duplicate commits.
3. Fetch: 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.
Symptom · 02
Ran git reset --hard and lost uncommitted work
→
Fix
1. Uncommitted working directory changes are NOT in reflog. They are permanently lost if not staged.
2. If the changes were staged before the reset: git fsck --unreachable | grep blob pull. Some had local may status before --hard.
Symptom · 03
Ran git reset --hard and lost committed work
→
Fix
Symptom · 04
Force-pushed after reset on shared branch — team has diverged branches
→
Fix
Symptom · 05
git reset HEAD <file> did not unstage the file
→
Fix
Symptom · 06
Used --hard when you meant --soft — changes appear gone
→
Fix
Git Reset Modes Compared
Mode
HEAD moves?
Index (staging)?
Working dir?
Data loss?
--soft
Yes
Unchanged (changes staged)
Unchanged
No
--mixed (default)
Yes
Cleared (changes unstaged)
Unchanged
No
--hard
Yes
Cleared
Reverted to target
Yes — uncommitted work lost
⚙ Quick Reference
11 commands from this guide
File
Command / Code
Purpose
01_reset_basics.sh
for i in 1 2 3; do echo 'Line '$i > file.txt && git add . && git commit -m 'Comm...
What Is Git Reset
iothecodeforgegitResetModes.sh
git log --oneline
The Three Modes
iothecodeforgegitResetVsRevert.sh
git reset --hard HEAD~1 # Undo last commit, discard changes
git reset vs git revert
iothecodeforgegitRecoverFromReset.sh
git reflog
Recovering from a Bad --hard Reset
DiscardLocalChanges.yml
git status
Undo Uncommitted Changes Before They Go Live
RevertSharedCommit.yml
git revert HEAD
Revert a Shared Commit Without Rewriting History
SurgicalFileRestore.yml
git status
Discard Uncommitted Changes to a File
revert-vs-reset-shared.yml
- branch: main
Why You Should Never git reset a Shared Branch
soft-reset-squash.yml
- commits:
git reset --soft
prerequisites_check.yml
- name: Verify current branch
Prerequisites
reset_to_remote.yml
- name: Fetch remote latest
Steps to Reset a Git Branch to a Remote Repository
git reset is for local branch cleanup. git revert is for undoing changes on shared branches
it adds a new commit rather than rewriting history.
3
git reflog saves you after a bad --hard reset
commits you reset past are still recoverable for 30 days via reflog. Uncommitted working directory changes are not.
4
Never force-push after resetting a shared branch. The correct tool for shared branches is always git revert.
5
Always run git status before --hard. If the working tree is not clean, stash first.
6
The --soft mode is the fastest way to squash local commits
reset --soft HEAD~N then commit once.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
What is the difference between git reset --hard and --soft?
Both move HEAD back to a previous commit. --soft keeps your changes staged in the index, ready to recommit. --hard discards all changes and reverts your working directory to match the target commit — any uncommitted work is permanently lost.
Was this helpful?
02
How do I undo the last commit without losing my changes?
Run git reset --soft HEAD~1 to undo the last commit while keeping all changes staged. Or git reset HEAD~1 (--mixed) to undo the commit and unstage the changes, leaving them in your working directory. Both are non-destructive.
Was this helpful?
03
When should I use git revert instead of git reset?
Use git revert on any branch that other people have already pulled from — main, develop, release branches. Revert creates a new commit that undoes the change without rewriting history. Reset rewrites history, which breaks teammates' local repos and requires a force push.
Was this helpful?
04
Can I recover work after git reset --hard?
Committed work is recoverable via git reflog for 30 days. Run git reflog to find the commit hash you reset past, then create a branch pointing to it. Uncommitted working directory changes are NOT recoverable via reflog — check your editor's local history (VS Code, IntelliJ) as a last resort.
Was this helpful?
05
What is the difference between git reset HEAD and git reset HEAD~1?
git reset HEAD <file> unstages a file from the staging index without changing the working directory. git reset HEAD~1 moves the branch pointer back one commit — it undoes the entire last commit. The tilde (~1) is the critical difference.