DEV Community

Muhammad Ikramullah Khan
Muhammad Ikramullah Khan

Posted on

Advanced Git: The Commands That Save Your Career

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

2. Mixed reset (keep changes, unstage):

git reset HEAD~1
Enter fullscreen mode Exit fullscreen mode

(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"
Enter fullscreen mode Exit fullscreen mode

3. Hard reset (delete everything):

git reset --hard HEAD~1
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

⚠️ 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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:

  1. Log into the service (AWS, Stripe, etc.)
  2. Revoke the exposed key
  3. Generate a new key
  4. 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
Enter fullscreen mode Exit fullscreen mode

Remove a file from all history:

# Remove config.py from all commits
git filter-repo --path config.py --invert-paths
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

--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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

Create .env file (gitignored):

# .env
API_KEY=sk_live_abc123xyz
DATABASE_URL=postgres://user:pass@localhost/db
Enter fullscreen mode Exit fullscreen mode

Add .env to .gitignore:

.env
.env.*
!.env.example
Enter fullscreen mode Exit fullscreen mode

Create .env.example (committed):

# .env.example
API_KEY=your_key_here
DATABASE_URL=your_database_url_here
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Viewing Stash Contents

# See what's in a stash
git stash show

# See detailed diff
git stash show -p
Enter fullscreen mode Exit fullscreen mode

Dropping Stashes

# Remove specific stash
git stash drop stash@{0}

# Remove all stashes
git stash clear
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Rebase:

git rebase -i HEAD~5
Enter fullscreen mode Exit fullscreen mode

In editor:

pick a1b2c3d Implement pagination
fixup d4e5f6g WIP: still working
reword g7h8i9j Add tests
fixup j1k2l3m Fix typo
fixup m4n5o6p Fix another typo
Enter fullscreen mode Exit fullscreen mode

After rebase:

git log --oneline

# Output:
# b2b2b2b Add comprehensive tests for pagination
# a1a1a1a Implement pagination with automatic page detection
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Or abort:

git cherry-pick --abort
Enter fullscreen mode Exit fullscreen mode

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

  1. Mark current commit as "bad" (has the bug)
  2. Mark an old commit as "good" (doesn't have the bug)
  3. Git checks out the middle commit
  4. You test: is the bug present?
  5. Mark it good or bad
  6. Git narrows the range
  7. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Found it! Commit d4e5f6g introduced the bug.

# Exit bisect
git bisect reset
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Amend and Edit Message

git commit --amend
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

Your commits are restored.

Reflog for Specific Branch

git reflog show feature/login
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Now you're on a branch. Can commit normally.

Option 2: Go back to a branch

# Return to main
git checkout main
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Safer Force Push

# Use --force-with-lease instead of --force
git push --force-with-lease
Enter fullscreen mode Exit fullscreen mode

This fails if someone else pushed while you were working. Prevents overwriting their commits.

When NOT to Force Push

Never force push to:

  • main or master branch
  • 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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

View All Aliases

git aliases
Enter fullscreen mode Exit fullscreen mode

Or:

git config --global --get-regexp alias
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Recovery

# See all HEAD movements
git reflog

# Recover deleted commit
git reset --hard <commit>

# Find what introduced a bug
git bisect start
Enter fullscreen mode Exit fullscreen mode

Cherry-Picking

# Copy commit to current branch
git cherry-pick <commit>

# Copy multiple commits
git cherry-pick <commit1> <commit2>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Scenario 2: Accidentally Deleted Branch

# Find last commit on deleted branch
git reflog

# Recreate branch
git branch recovered-branch <commit>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Don't panic
  2. Check git reflog
  3. Find the state before you broke it
  4. Reset or create branch there
  5. 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:

Top comments (0)