

Look, preventing disasters is better than fixing them. Here are the habits that'll save you from most Git-related panic attacks.
Core Principle: Protect Shared History
The golden rule: Don't rewrite commits that other people have pulled. Use git revert
for shared commits, save the reset commands for your own local fuckups.
Why this matters: History rewriting destroys other developers' local repositories. Force-push to a shared branch and you'll break CI/CD pipelines and piss off your entire team.
The rule in practice:
- Feature branch you're working on alone? Reset all you want
- Main branch? Never touch it with history rewriting
- Shared feature branch? Check with your teammates first
Branch Safety Guidelines (Hard-Earned Wisdom)
Feature Branches: Your Personal Playground
Go nuts. Reset, rebase, amend - nobody cares until you merge.
## Safe to do on feature branches:
git reset --soft HEAD~3 # Undo last 3 commits
git rebase -i HEAD~5 # Interactive cleanup
git commit --amend # Fix typos
git push --force-with-lease origin feature-branch # Update remote
Practical application: Feature branches often accumulate many small commits during development. Use git rebase -i HEAD~10
to squash and reorganize these into logical commits before merging. This creates clean, reviewable history without affecting shared branches.
Main Branch: Treat It Like Production
Because it basically is production. Protected branches should be mandatory, not optional.
## Only safe operations on main:
git revert abc123f # Creates new commit that undoes changes
git merge --no-ff feature-branch # Preserves branch history
Protection settings I actually use:
Pre-Disaster Checklist

Before doing anything scary, run this mental checklist:
## Where the fuck am I?
git branch --show-current && git status
## What commits am I about to mess with?
git log --oneline -5
## Create a panic button
git branch oh-shit-backup-$(date +%Y%m%d-%H%M%S)
## Stash any work in progress
git stash push -m \"WIP before I potentially break everything\"
Why this works: I've used backup branches to unfuck myself at least 30 times. Creating branches costs nothing, but panic attacks are expensive.
Advanced Recovery: When Everything Goes to Shit

Nuclear Disaster Recovery
The scenario: You accidentally git reset --hard HEAD~10
and lost two weeks of work.
## Find your lost commits (don't panic yet)
git reflog --all --oneline
## Look for your work (example output):
## a1b2c3d HEAD@{5}: commit: Fix user authentication bug
## e4f5g6h HEAD@{6}: commit: Add password validation
## i9j0k1l HEAD@{7}: commit: Refactor auth module
## Restore to before you fucked up
git reset --hard a1b2c3d
## Or cherry-pick specific commits if the history is messy
git cherry-pick e4f5g6h i9j0k1l
Time-based recovery (when you remember approximately when things were working):
## Find commits from specific times
git reflog --since=\"2 hours ago\"
git reflog --until=\"yesterday 5pm\"
## Restore deleted branches
git branch recovered-feature HEAD@{10}
Interactive Rebase: The Swiss Army Knife
When your commit history looks like a toddler's art project:
git rebase -i HEAD~5
In the editor:
pick
→ keep commit as-is
reword
→ change commit message
edit
→ pause to modify commit
squash
→ combine with previous commit
drop
→ delete commit entirely
Personal disaster: This was during the log4j panic of December 2021 when everyone was updating everything. I had 47 commits for a single security patch, including masterpieces like "fix", "fix fix", "actually fix", and "fuck it, this works". Interactive rebase saved my dignity by collapsing them into 4 meaningful commits before the emergency security review.
Team Coordination: Don't Be That Guy
When You Must Rewrite Shared History
Sometimes you have no choice. Here's how not to make enemies:
- Announce in advance: "I need to rewrite feature-branch history in 1 hour"
- Give exact commands: Don't make people guess how to fix their local state
- Pick a time when nobody's actively working: Not during standup or right before deployment
- Check that everyone's updated: Before you continue working
Commands for teammates after you rewrite:
## If they have no local commits on the branch:
git fetch origin
git reset --hard origin/feature-branch
## If they have local commits:
git fetch origin
git rebase origin/feature-branch # May need conflict resolution
Merge vs Rebase Strategy (Choose One, Stick With It)
My team's rule: Rebase feature branches, merge to main.
## Feature branch workflow:
git checkout feature-branch
git rebase main # Clean up history
git checkout main
git merge --no-ff feature-branch # Preserve branch context
Why this works: Clean linear history on features, but you can still see where features were integrated. This approach is recommended by Atlassian, used by GitHub, and preferred by many enterprise teams.
Configuration That Prevents Disasters
## Don't accidentally push to main
git config --global push.default simple
## Auto-stash during rebase (saves your ass)
git config --global rebase.autoStash true
## See more context in diffs
git config --global diff.context 10
## Keep reflog longer (default 90 days is too short)
git config --global gc.reflogExpire \"1 year\"
## Enable reflog for all branches, not just HEAD
git config --global core.logAllRefUpdates true
## Show more context in merge conflicts (Git 2.35+)
git config --global merge.conflictStyle zdiff3
## Use better algorithm for renames (Git 2.18+)
git config --global diff.renames copies
Aliases that have saved my ass:
git config --global alias.unfuck 'reset --soft HEAD~1'
git config --global alias.nuke 'reset --hard HEAD~1'
git config --global alias.panic 'branch panic-backup-$(date +%Y%m%d-%H%M%S)'
git config --global alias.wip 'commit -am \"WIP: work in progress\"'
git config --global alias.uncommit 'reset --mixed HEAD~1'
git config --global alias.fucked 'reflog --oneline'
git config --global alias.scorched-earth '!f() { git fetch origin && git reset --hard origin/main; }; f'
CI/CD Protection
GitHub Actions that prevent disasters:
name: Prevent Force Push to Main
on:
push:
branches: [main]
jobs:
check-force-push:
runs-on: ubuntu-latest
steps:
- name: Fail if force push
run: |
if [[ \"${{ github.event.forced }}\" == \"true\" ]]; then
echo \"❌ Force push to main is not allowed\"
echo \"Use 'git revert' instead of rewriting history\"
exit 1
fi
Branch protection webhook (for when GitHub settings aren't enough):
#!/bin/bash
## git-pre-push-hook.sh
protected_branch='main'
current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,')
if [ $protected_branch = $current_branch ]; then
echo \"❌ Direct push to main branch is not allowed\"
echo \"Create a pull request instead\"
exit 1
fi
Repository health monitoring (inspired by GitHub's practices and GitLab's repo management):
#!/bin/bash
## check-git-health.sh - Run this weekly
echo \"🔍 Checking repository health...\"
## Check for large files that shouldn't be tracked
git rev-list --objects --all | \
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
awk '/^blob/ {print substr($0,6)}' | \
sort --numeric-sort --key=2 | \
tail -10
## Check reflog usage
echo -e \"
📊 Reflog entries per branch:\"
git for-each-ref --format='%(refname:short)' | while read branch; do
count=$(git reflog show --date=short \"$branch\" 2>/dev/null | wc -l)
echo \"$branch: $count entries\"
done
## Warn about uncommitted changes
if ! git diff --quiet || ! git diff --cached --quiet; then
echo -e \"
⚠️ Uncommitted changes detected\"
git status --porcelain
fi
Configuration for Safety
These Git settings reduce the likelihood of commit mistakes:
## Prevent accidental pushes to main
git config --global push.default simple
## Auto-stash during rebase to preserve work
git config --global rebase.autoStash true
## Extend reflog retention beyond 90 days
git config --global gc.reflogExpire \"1 year\"
## Better merge conflict display (Git 2.35+)
git config --global merge.conflictStyle zdiff3
Useful aliases for common operations:
git config --global alias.undo 'reset --soft HEAD~1'
git config --global alias.unstage 'reset HEAD~1'
git config --global alias.panic 'branch panic-backup'
The Bottom Line: You Can Unfuck Almost Anything
Remember the promise from the beginning? That you'd learn to undo commits without losing two weeks of work? Here's what actually matters:
The Three-Second Rule: Check which of Git's three areas your changes should land in:
- Staging area (
--soft
) → Ready to commit again with changes
- Working directory (
--mixed
) → Pick and choose what to keep
- Nowhere (
--hard
) → Nuclear option when you want everything gone
The Golden Rules That Actually Matter:
- Local commits: Reset all you want, nobody gets hurt
- Pushed commits: Use
git revert
or coordinate with your team
- When in doubt: Create a backup branch first (
git branch oh-shit-backup
)
- When you're fucked: Check
git reflog
- your work is probably still there
The Real Recovery Truth: Git's reflog keeps track of everything for 90 days. Even when you think you've "deleted" commits with git reset --hard
, they're still hiding there. I've recovered from disasters that looked career-ending just by understanding this safety net.
You're Now Unfuck-Proof: With these commands and the understanding of Git's three areas plus reflog recovery, you can handle any commit disaster. The next time someone on your team accidentally force-pushes to main or loses two weeks of work, you'll be the hero with the recovery commands.
Keep this page bookmarked. You'll need it at 3 AM when everything's broken and you're the only one awake to fix it.
Remember that moment of panic when you just committed something catastrophically wrong? That's now in your past. You have the tools to handle any Git disaster: understanding the three areas where your code lives, knowing which reset command matches your goal, and trusting the reflog safety net to save you when everything looks lost.
The next time someone on your team accidentally nukes a feature branch or commits their grocery list to production, you'll be the one with the calm voice saying "I can fix this" while everyone else is having a panic attack. That's the power of actually understanding Git's undo operations instead of just hoping for the best.