You just committed your API keys to GitHub. All of them. AWS keys, database passwords, Stripe API secrets. Everything.
You pushed before you realized. The commit is on GitHub. Public repository. Anyone can see it.
You delete the commit locally. Push again. But it's too late. The keys are in the Git history. Forever. Anyone can checkout that old commit and see your secrets.
You're panicking. Do you delete the entire repository? Start over? Hope nobody noticed?
Or this happens. You've been working on a feature for three days. Made 15 commits. Then you realize you've been committing to main instead of a feature branch. Your production code now has half-finished, untested features mixed in.
Or this one. You accidentally deleted a file. Committed the deletion. Pushed it. Two days later, you need that file back. But it's gone. Or is it?
These situations feel impossible. Like you broke Git permanently. Like you need to start the project over.
You don't. Git has commands for these exact scenarios. Commands that rewrite history. Recover deleted code. Remove secrets from every commit. Undo mistakes that seem permanent.
These are the advanced Git commands. The ones you hope you never need. But when you need them, they save everything.
Let me show you.
The Three Ways to Undo: Reset vs Revert vs Rebase
Git has three main ways to undo commits. They do different things.
Git Reset: Rewind Time
What it does: Moves the branch pointer backward, like the commits never happened.
When to use: Undo commits that you haven't pushed yet (local only).
Three modes:
1. Soft reset (keep changes, uncommit):
git reset --soft HEAD~1
This uncommits the last commit but keeps all changes staged. Like you never ran git commit.
Use case: Wrong commit message. Reset, recommit with correct message.
# You committed with bad message
git commit -m "fixed stuff"
# Reset (keeps changes staged)
git reset --soft HEAD~1
# Commit again with good message
git commit -m "Fix pagination bug in product scraper"
2. Mixed reset (keep changes, unstage):
git reset HEAD~1
(Default mode, same as --mixed)
This uncommits AND unstages, but keeps changes in your working directory. Like you never ran git add or git commit.
Use case: Committed too early. Want to modify code before committing.
# You committed but forgot to add error handling
git commit -m "Add login feature"
# Reset (keeps changes, unstages them)
git reset HEAD~1
# Add error handling
echo "try/except" >> login.py
# Now commit everything together
git add .
git commit -m "Add login feature with error handling"
3. Hard reset (delete everything):
git reset --hard HEAD~1
This deletes the commit AND all changes. Gone. Like the work never happened.
Use case: Completely undo bad work.
# You tried a feature, it doesn't work, delete everything
git reset --hard HEAD~1
# All changes from that commit are gone
⚠️ Warning: --hard is destructive. You lose uncommitted work.
Git Revert: Create Opposite Commit
What it does: Creates a new commit that undoes a previous commit.
When to use: Undo commits that you already pushed (public commits).
Example:
# You have these commits:
# C: Add pagination
# B: Add error handling
# A: Initial commit
# Pagination introduced a bug
# Undo commit C (keep B and A)
git revert C
This creates commit D that removes everything commit C added.
Why not reset? Reset rewrites history. If you already pushed, teammates have the old history. Reset causes chaos.
Revert creates new history on top. Everyone can pull it cleanly.
Example:
# See commit history
git log --oneline
# Output:
# d4e5f6g Add pagination
# c3d4e5f Add error handling
# b2c3d4e Initial commit
# Revert the pagination commit
git revert d4e5f6g
# New commit created that undoes pagination
git log --oneline
# Output:
# a1a1a1a Revert "Add pagination"
# d4e5f6g Add pagination
# c3d4e5f Add error handling
# b2c3d4e Initial commit
The pagination code is gone, but the history shows you added it and then reverted it. Transparent.
Git Rebase: Rewrite History
What it does: Rewrites commits to change their history, order, or content.
When to use: Clean up messy commit history before pushing.
Example:
# You have messy commits:
# E: Fix typo
# D: Fix another typo
# C: Actually add the feature
# B: WIP progress
# A: Start feature
# Rebase to combine into one clean commit
git rebase -i HEAD~5
This opens an editor showing all 5 commits. You can:
- Squash (combine commits)
- Reword (change commit message)
- Reorder (change sequence)
- Drop (delete commits)
We'll cover this in detail later.
Quick Reference
| Command | Changes History? | Use When | Safety |
|---|---|---|---|
reset --soft |
Yes | Recommit with better message | Safe (keeps changes) |
reset --mixed |
Yes | Modify before committing | Safe (keeps changes) |
reset --hard |
Yes | Delete bad work completely | DANGEROUS (loses work) |
revert |
No | Undo public commits | Safe |
rebase |
Yes | Clean up before pushing | Medium (rewrite history) |
Golden rule: Never rewrite history that you've pushed and others have pulled. Use revert instead.
Recovering Deleted Commits
You ran git reset --hard and deleted commits. Then realized you needed them.
Good news: Git keeps deleted commits for ~30 days. You can recover them.
The Reflog: Git's Safety Net
Git logs every HEAD movement in the reflog (reference log).
git reflog
Output:
a1b2c3d HEAD@{0}: reset: moving to HEAD~3
d4e5f6g HEAD@{1}: commit: Add pagination
c3d4e5f HEAD@{2}: commit: Add error handling
b2c3d4e HEAD@{3}: commit: Initial feature
Every action is logged. Even the reset that deleted commits.
Recovering a Deleted Commit
Scenario: You reset and deleted commits. You want them back.
# See what you deleted
git reflog
# Find the commit you want (before the reset)
# Let's say it's d4e5f6g
# Create a branch at that commit
git branch recovered d4e5f6g
# Or reset back to it
git reset --hard d4e5f6g
Your "deleted" commits are back.
Real Example
# You're working on a feature
git commit -m "Implement login"
git commit -m "Add validation"
git commit -m "Add error handling"
# You realize you're on main, not a feature branch
# Panic. Delete the commits.
git reset --hard HEAD~3
# Oh no, you needed those commits!
# Check reflog
git reflog
# Output:
# a1a1a1a HEAD@{0}: reset: moving to HEAD~3
# b2b2b2b HEAD@{1}: commit: Add error handling
# c3c3c3c HEAD@{2}: commit: Add validation
# d4d4d4d HEAD@{3}: commit: Implement login
# Create feature branch with those commits
git checkout -b feature/login d4d4d4d
# Or, get the exact commit hash
git cherry-pick d4d4d4d c3c3c3c b2b2b2b
You recovered everything.
How Long Does Reflog Keep Deleted Commits?
Default: 90 days for reachable commits, 30 days for unreachable (deleted) commits.
After that, Git garbage collects them. They're truly gone.
Lesson: If you deleted something, recover it within 30 days.
Removing Secrets from History
You committed API keys. Pushed to GitHub. Now you need to remove them from every commit in history.
Why Deleting the Commit Isn't Enough
# You committed secrets
git commit -m "Add API integration"
# File contains: API_KEY = "sk_live_abc123xyz"
# You pushed to GitHub
git push
# You realize the mistake
# You remove the key and commit again
sed -i 's/API_KEY = .*/API_KEY = os.getenv("API_KEY")/' config.py
git commit -m "Remove hardcoded API key"
git push
Problem: The key is still in the history. Anyone can do:
git checkout <previous-commit>
cat config.py
# They see: API_KEY = "sk_live_abc123xyz"
The secret is in the Git database forever (unless you rewrite history).
Step 1: Rotate the Secret Immediately
Before fixing Git, make the exposed secret useless.
For API keys:
- Log into the service (AWS, Stripe, etc.)
- Revoke the exposed key
- Generate a new key
- Update your local config (not in Git)
Now the exposed key is worthless. Even if someone finds it in Git history, it won't work.
Step 2: Remove Secret from Git History
Use git filter-branch or the newer git filter-repo.
Install git-filter-repo:
# Mac
brew install git-filter-repo
# Linux
pip install git-filter-repo
# Windows
pip install git-filter-repo
Remove a file from all history:
# Remove config.py from all commits
git filter-repo --path config.py --invert-paths
This rewrites every commit, removing config.py as if it never existed.
Remove specific text from all files:
# Remove the API key pattern from all files
git filter-repo --replace-text <(echo 'sk_live_abc123xyz==>REMOVED')
This finds sk_live_abc123xyz in every file in every commit and replaces it with REMOVED.
Step 3: Force Push
# This rewrites GitHub history
git push --force-with-lease origin main
--force-with-lease is safer than --force. It fails if someone else pushed while you were rewriting.
Step 4: Tell Collaborators
Everyone who cloned the repo needs to re-clone:
# Don't pull, re-clone
rm -rf project-name
git clone https://github.com/you/project-name.git
Pulling won't work. The history is different.
Alternative: BFG Repo-Cleaner
BFG is faster than filter-repo for large repos.
Install:
# Mac
brew install bfg
# Or download jar
wget https://repo1.maven.org/maven2/com/madgag/bfg/1.14.0/bfg-1.14.0.jar
Remove passwords:
# Create passwords.txt with secrets to remove
echo "sk_live_abc123xyz" > passwords.txt
echo "password123" >> passwords.txt
# Run BFG
bfg --replace-text passwords.txt
# Clean up
git reflog expire --expire=now --all
git gc --prune=now --aggressive
# Force push
git push --force origin main
BFG is 10-100x faster than filter-branch on large repositories.
Prevention: Use Environment Variables
Never commit secrets. Use environment variables instead.
# Bad (hardcoded)
API_KEY = "sk_live_abc123xyz"
# Good (environment variable)
import os
API_KEY = os.getenv("API_KEY")
Create .env file (gitignored):
# .env
API_KEY=sk_live_abc123xyz
DATABASE_URL=postgres://user:pass@localhost/db
Add .env to .gitignore:
.env
.env.*
!.env.example
Create .env.example (committed):
# .env.example
API_KEY=your_key_here
DATABASE_URL=your_database_url_here
Teammates copy .env.example to .env and fill in their own secrets.
Load in Python:
from dotenv import load_dotenv
import os
load_dotenv()
API_KEY = os.getenv("API_KEY")
Secrets never touch Git.
Git Stash: Temporary Storage
You're working on a feature. Halfway done. Your boss says: "Fix this production bug NOW."
You can't commit (code doesn't work yet). You can't switch branches (changes would come with you).
Solution: Stash.
Basic Stashing
# You're working on feature
echo "half-finished code" >> feature.py
# Emergency! Need to fix bug
# Save work temporarily
git stash
# Your working directory is clean
git status
# Output: nothing to commit, working tree clean
# Switch to main, fix bug
git checkout main
# (fix bug, commit, push)
# Back to feature branch
git checkout feature/my-feature
# Restore your work
git stash pop
Your half-finished code is back. Like you never left.
Stash with Description
# Stash with message
git stash save "WIP: half-done pagination feature"
# List stashes
git stash list
# Output:
# stash@{0}: On feature/pagination: WIP: half-done pagination feature
# stash@{1}: On main: WIP: fixing typo
Multiple Stashes
# Stash current work
git stash save "Feature A progress"
# Work on something else
echo "other work" >> other.py
# Stash that too
git stash save "Feature B progress"
# List all stashes
git stash list
# Apply specific stash
git stash apply stash@{1} # Applies Feature A
# Pop removes from stash list, apply keeps it
Stashing Untracked Files
By default, stash only saves tracked files (files Git knows about).
# Stash including untracked files
git stash -u
# Or
git stash --include-untracked
Viewing Stash Contents
# See what's in a stash
git stash show
# See detailed diff
git stash show -p
Dropping Stashes
# Remove specific stash
git stash drop stash@{0}
# Remove all stashes
git stash clear
Real Example: Context Switching
# Working on scraper feature
vim scraper.py
# (edit, edit, edit... not done yet)
# Boss: "Production is down! Fix now!"
git stash save "Half-done scraper refactor"
# Fix production
git checkout main
vim hotfix.py
git add hotfix.py
git commit -m "Fix production crash"
git push
# Back to feature
git checkout feature/scraper-refactor
git stash pop
# Continue where you left off
vim scraper.py
Stash lets you context-switch without losing work or committing garbage.
Interactive Rebase: Rewriting History
You made 10 commits while building a feature. Half are typo fixes. Messages say "WIP" and "test". You want to clean this up before pushing.
Interactive rebase lets you:
- Combine (squash) commits
- Reword messages
- Reorder commits
- Delete commits
- Edit commits
Basic Interactive Rebase
# Rebase last 5 commits
git rebase -i HEAD~5
This opens an editor:
pick a1b2c3d Implement login
pick d4e5f6g Fix typo
pick g7h8i9j Actually fix typo
pick j1k2l3m Add validation
pick m4n5o6p WIP
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit message
# e, edit = use commit, but stop to amend
# s, squash = use commit, meld into previous
# f, fixup = like squash, discard commit message
# d, drop = remove commit
Example 1: Squash Typo Fixes
pick a1b2c3d Implement login
fixup d4e5f6g Fix typo
fixup g7h8i9j Actually fix typo
pick j1k2l3m Add validation
fixup m4n5o6p WIP
This combines commits:
- Login implementation + 2 typo fixes = 1 commit
- Validation + WIP = 1 commit
Result: 2 clean commits instead of 5 messy ones.
Example 2: Reword Messages
pick a1b2c3d Implement login
reword d4e5f6g Fix typo
pick g7h8i9j Add validation
Git will pause at d4e5f6g and let you change the message to something better like "Add input validation for login form".
Example 3: Reorder Commits
pick g7h8i9j Add validation
pick a1b2c3d Implement login
pick d4e5f6g Fix typo
Commits will be reordered. Validation comes first, then login, then typo fix.
⚠️ Careful: Reordering can cause conflicts if commits depend on each other.
Example 4: Drop Commits
pick a1b2c3d Implement login
drop d4e5f6g Debug commit (remove this)
pick g7h8i9j Add validation
The debug commit disappears from history.
Real Example: Cleaning Feature Branch
Before rebase:
git log --oneline
# Output:
# m4n5o6p Fix another typo
# j1k2l3m Fix typo
# g7h8i9j Add tests
# d4e5f6g WIP: still working
# a1b2c3d Implement pagination
Rebase:
git rebase -i HEAD~5
In editor:
pick a1b2c3d Implement pagination
fixup d4e5f6g WIP: still working
reword g7h8i9j Add tests
fixup j1k2l3m Fix typo
fixup m4n5o6p Fix another typo
After rebase:
git log --oneline
# Output:
# b2b2b2b Add comprehensive tests for pagination
# a1a1a1a Implement pagination with automatic page detection
Two clean commits. Ready to push.
When to Use Interactive Rebase
Good times:
- Before pushing a feature branch
- Cleaning up messy local history
- Combining "fix typo" commits
- Rewriting bad commit messages
Bad times:
- After pushing (if others pulled it)
- On the main branch
- On shared branches
Rule: Only rebase commits you haven't pushed.
Cherry-Picking: Copying Commits
You have a commit on one branch. You want it on another branch. Cherry-pick copies it.
Basic Cherry-Pick
# On branch A, you made a bugfix
git checkout branch-a
git commit -m "Fix critical bug"
# Commit hash: d4e5f6g
# Switch to branch B
git checkout branch-b
# Copy the bugfix commit to branch B
git cherry-pick d4e5f6g
Branch B now has that bugfix, as if you made the commit there.
Real Example: Hotfix to Multiple Branches
Scenario: You have three branches:
-
main(production) -
develop(integration) -
feature/new-ui(in progress)
You fixed a critical bug on main. Need it on all branches.
# Fix on main
git checkout main
vim bugfix.py
git commit -m "Fix security vulnerability"
# Commit: a1b2c3d
# Apply to develop
git checkout develop
git cherry-pick a1b2c3d
# Apply to feature branch
git checkout feature/new-ui
git cherry-pick a1b2c3d
All three branches have the fix.
Cherry-Picking Multiple Commits
# Pick commits a1b2c3d, d4e5f6g, and g7h8i9j
git cherry-pick a1b2c3d d4e5f6g g7h8i9j
# Pick a range
git cherry-pick a1b2c3d..g7h8i9j
Handling Cherry-Pick Conflicts
Sometimes the commit doesn't apply cleanly.
git cherry-pick d4e5f6g
# Output:
# CONFLICT (content): Merge conflict in scraper.py
# error: could not apply d4e5f6g... Fix bug
Resolve like a merge conflict:
# Edit scraper.py, fix conflicts
vim scraper.py
# Mark resolved
git add scraper.py
# Continue cherry-pick
git cherry-pick --continue
Or abort:
git cherry-pick --abort
When to Use Cherry-Pick
Good uses:
- Applying hotfixes to multiple branches
- Copying specific features between branches
- Recovering a commit from a deleted branch
Bad uses:
- Copying many commits (use merge or rebase instead)
- As a replacement for proper branching
Git Bisect: Finding the Bug-Introducing Commit
Your code worked last week. Now it's broken. You have 100 commits since then. Which one broke it?
Bisect does binary search through commits to find the bug.
How Bisect Works
- Mark current commit as "bad" (has the bug)
- Mark an old commit as "good" (doesn't have the bug)
- Git checks out the middle commit
- You test: is the bug present?
- Mark it good or bad
- Git narrows the range
- Repeat until you find the exact commit that introduced the bug
Using Bisect
# Start bisect
git bisect start
# Current commit is bad (has bug)
git bisect bad
# Commit from last week was good
git bisect good a1b2c3d
Git checks out a commit in the middle.
# Test the code
python scraper.py
# If bug is present:
git bisect bad
# If bug is NOT present:
git bisect good
Git checks out another commit. Repeat testing.
Eventually:
d4e5f6g is the first bad commit
commit d4e5f6g
Author: John Doe
Date: Mon Mar 18 14:32:10 2024
Refactor pagination logic
:100644 100644 a1a1a1a b2b2b2b M scraper.py
Found it! Commit d4e5f6g introduced the bug.
# Exit bisect
git bisect reset
Automated Bisect
If you have a test script:
# test.sh returns 0 if good, 1 if bad
#!/bin/bash
python scraper.py
# Exit 0 if works, exit 1 if broken
# Run bisect automatically
git bisect start
git bisect bad
git bisect good a1b2c3d
git bisect run ./test.sh
Git runs the script on each commit. Finds the bug automatically.
Real Example
# Pagination worked last Friday
# Today (Monday) it's broken
# 50 commits since Friday
git bisect start
git bisect bad # Current commit is bad
# Find Friday's commit
git log --since="5 days ago" --oneline
# Mark Friday's last commit as good
git bisect good f1f1f1f
# Git checks out middle commit
# Test:
python scraper.py --test-pagination
# Broken
git bisect bad
# Git checks out another
# Test:
python scraper.py --test-pagination
# Works!
git bisect good
# Continue until:
# a7a7a7a is the first bad commit
# "Optimize page detection logic"
# Found the commit that broke pagination
git bisect reset
# Fix the bug in that commit's code
git revert a7a7a7a
Bisect saved you from manually checking 50 commits.
Amending Commits
You committed. Then immediately realized you forgot something.
Amend Last Commit
# You committed
git commit -m "Add login feature"
# Forgot to add a file
echo "validation code" >> validate.py
# Add it to the last commit
git add validate.py
git commit --amend --no-edit
The file is now part of the last commit. Commit message unchanged.
Amend Commit Message
# Bad message
git commit -m "stuff"
# Fix it
git commit --amend -m "Implement user authentication with JWT tokens"
Amend and Edit Message
git commit --amend
Opens editor. You can change message and review what's being committed.
⚠️ Warning About Amending
Only amend commits you haven't pushed.
If you already pushed and then amend, you'll need to force push:
git push --force
This rewrites history on GitHub. Bad for collaboration.
Better: Just make a new commit fixing the mistake.
Git Reflog: Complete History
Reflog records every HEAD movement. It's your safety net.
Viewing Reflog
git reflog
Output:
a1b2c3d HEAD@{0}: commit: Add feature
d4e5f6g HEAD@{1}: checkout: moving from main to feature
g7h8i9j HEAD@{2}: commit: Fix bug
j1k2l3m HEAD@{3}: reset: moving to HEAD~1
Every checkout, commit, reset, merge, rebase... everything.
Using Reflog to Undo
Scenario: You ran git reset --hard and deleted 5 commits. You want them back.
# Check reflog
git reflog
# Find where you were before reset
# HEAD@{1}: commit: Last good commit
# Go back to that state
git reset --hard HEAD@{1}
Your commits are restored.
Reflog for Specific Branch
git reflog show feature/login
Shows all movements on that branch.
Reflog Expiration
Reflog keeps entries for:
- 90 days (reachable commits)
- 30 days (unreachable/deleted commits)
After that, Git garbage collects them.
Check what will be deleted:
git reflog expire --expire-unreachable=now --all --dry-run
Advanced Reset Techniques
Reset to Specific Commit
# Go back 3 commits
git reset --hard HEAD~3
# Go to specific commit
git reset --hard a1b2c3d
# Go to state 5 moves ago in reflog
git reset --hard HEAD@{5}
Reset Specific File
Undo changes to one file without affecting others.
# Unstage a file
git reset HEAD scraper.py
# Restore file to last commit
git checkout HEAD scraper.py
# Or (newer syntax)
git restore scraper.py
Reset Remote Branch
Your local main is ahead of GitHub. You want to match GitHub exactly.
# Fetch latest
git fetch origin
# Reset local to match remote
git reset --hard origin/main
All local commits ahead of remote are gone.
Handling "Detached HEAD" State
You checkout an old commit. Git says: "You are in 'detached HEAD' state."
What this means: You're looking at an old commit. Any new commits won't be on a branch.
Getting Out of Detached HEAD
Option 1: Create a branch here
# You're at an old commit
git checkout a1b2c3d
# Detached HEAD warning appears
# Create branch to save this state
git checkout -b recover-old-feature
Now you're on a branch. Can commit normally.
Option 2: Go back to a branch
# Return to main
git checkout main
You're back on a branch.
Using Detached HEAD Safely
Detached HEAD is useful for exploring old code:
# Look at code from last month
git log --since="1 month ago"
git checkout <commit-from-last-month>
# Explore, test, read
cat old_file.py
# Return to present
git checkout main
Nothing breaks. It's just exploring.
Force Push (And When NOT To)
Force push overwrites remote history with local history.
When Force Push is Needed
After rewriting local history:
# You rebased or amended
git rebase -i HEAD~5
# Push fails (history changed)
git push
# Error: rejected, tip of branch is behind
# Force push (overwrites remote)
git push --force
Safer Force Push
# Use --force-with-lease instead of --force
git push --force-with-lease
This fails if someone else pushed while you were working. Prevents overwriting their commits.
When NOT to Force Push
❌ Never force push to:
-
mainormasterbranch - Shared branches with active collaborators
- Public branches others depend on
✅ Safe to force push:
- Your own feature branches
- Branches only you work on
- Before creating a pull request (cleaning up)
Protecting Main Branch
On GitHub: Settings → Branches → Add rule
- Branch name:
main - ✅ Restrict who can push to matching branches
- ✅ No force pushes
Now main is protected. Nobody can force push to it.
Git Aliases: Shortcuts for Long Commands
Save time with aliases.
Creating Aliases
# Short log
git config --global alias.lg "log --oneline --graph --all"
# Now use:
git lg
# Status
git config --global alias.st status
# Undo last commit (soft)
git config --global alias.undo "reset --soft HEAD~1"
Useful Aliases
# Pretty log
git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
# Show unstaged changes
git config --global alias.df "diff"
# Show staged changes
git config --global alias.dfs "diff --staged"
# List aliases
git config --global alias.aliases "config --get-regexp alias"
# Amend without editing message
git config --global alias.amend "commit --amend --no-edit"
# Delete merged branches
git config --global alias.cleanup "!git branch --merged | grep -v '\\*\\|main\\|develop' | xargs -n 1 git branch -d"
View All Aliases
git aliases
Or:
git config --global --get-regexp alias
Quick Reference: Advanced Commands
Undoing
# Undo last commit (keep changes)
git reset --soft HEAD~1
# Undo last commit (discard changes)
git reset --hard HEAD~1
# Undo pushed commit (create opposite)
git revert <commit>
Stashing
# Save work temporarily
git stash
# Save with message
git stash save "WIP feature"
# List stashes
git stash list
# Apply and remove from stash
git stash pop
# Apply but keep in stash
git stash apply
Rewriting History
# Interactive rebase (last 5 commits)
git rebase -i HEAD~5
# Amend last commit
git commit --amend
# Amend without editing message
git commit --amend --no-edit
Recovery
# See all HEAD movements
git reflog
# Recover deleted commit
git reset --hard <commit>
# Find what introduced a bug
git bisect start
Cherry-Picking
# Copy commit to current branch
git cherry-pick <commit>
# Copy multiple commits
git cherry-pick <commit1> <commit2>
Force Operations
# Force push (dangerous)
git push --force
# Safer force push
git push --force-with-lease
# Reset to remote state
git reset --hard origin/main
Emergency Scenarios
Scenario 1: Committed Secrets
# 1. Rotate the secret immediately
# 2. Remove from history
git filter-repo --replace-text <(echo 'SECRET_KEY==>REMOVED')
# 3. Force push
git push --force-with-lease
# 4. Tell team to re-clone
Scenario 2: Accidentally Deleted Branch
# Find last commit on deleted branch
git reflog
# Recreate branch
git branch recovered-branch <commit>
Scenario 3: Committed to Wrong Branch
# Create branch with commits
git branch feature-branch
# Remove commits from current branch
git reset --hard HEAD~3
# Switch to feature branch
git checkout feature-branch
Scenario 4: Messed Up Rebase
# Abort rebase
git rebase --abort
# Or find pre-rebase state in reflog
git reflog
git reset --hard HEAD@{before-rebase}
Scenario 5: Need Old Version of File
# Get file from specific commit
git show <commit>:path/to/file.py > file.py
# Or checkout just that file
git checkout <commit> -- path/to/file.py
Summary
Advanced Git saves you from disasters.
Undoing commits:
-
reset --soft- Uncommit, keep changes staged -
reset --mixed- Uncommit, keep changes unstaged -
reset --hard- Delete everything (dangerous) -
revert- Create opposite commit (safe for pushed commits) -
rebase -i- Rewrite history (before pushing)
Recovery:
-
reflog- See all actions, recover deleted commits -
bisect- Find bug-introducing commit -
cherry-pick- Copy commits between branches
Temporary storage:
-
stash- Save work temporarily, switch context
Fixing mistakes:
-
commit --amend- Fix last commit -
filter-repo- Remove secrets from history -
force-with-lease- Safer force push
Golden rules:
- Never rewrite pushed history (unless emergency)
- Always rotate secrets before removing from Git
- Use reflog when you think you lost commits
- Stash before switching context
- Test before force pushing
When things go wrong:
- Don't panic
- Check
git reflog - Find the state before you broke it
- Reset or create branch there
- Commits are rarely truly lost
Git's safety nets (reflog, stash, revert) mean you can experiment confidently. Even "permanent" mistakes are usually recoverable.
You now know Git from basics to advanced. You can collaborate, fix mistakes, and recover from disasters.
The Complete Git Journey
Blog 1: Local Git basics (commits, history)
Blog 2: GitHub (cloud backup, portfolio)
Blog 3: Branches (parallel development)
Blog 4: Collaboration (teams, open source)
Blog 5: Advanced commands (fixing everything)
You've learned:
- Version control fundamentals
- Cloud backup and sync
- Safe experimentation with branches
- Working with others without chaos
- Recovering from any mistake
What's next?
Use Git every day. Make mistakes. Fix them with these commands. The more you use Git, the more natural it becomes.
Git isn't just a tool. It's a safety net. It's a time machine. It's collaboration infrastructure.
Master Git, and you'll never lose code again.
Resources:
- Oh Shit, Git!: https://ohshitgit.com/
- Git Flight Rules: https://github.com/k88hudson/git-flight-rules
- Pro Git Book: https://git-scm.com/book/en/v2
- Learn Git Branching: https://learngitbranching.js.org/
Top comments (0)