Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
Git

Git: Undo Last Commit Without Losing Changes (3 Different Scenarios)

- 03.02.26 - ErcanOPAK

Committed too early or to wrong branch? Here’s how to undo commits without losing your work, depending on whether you’ve pushed or not.

Scenario 1: Undo Last Commit (Not Pushed Yet)

# Keep changes in working directory (most common)
git reset --soft HEAD~1

# Now your changes are uncommitted, ready to re-commit
# Use case: Fix commit message or add forgotten files

# Example workflow:
git reset --soft HEAD~1
# Add forgotten file
git add forgotten-file.js
# Re-commit with better message
git commit -m "Better commit message"

Scenario 2: Undo Commit and Unstage Files

# Keep changes but unstage them
git reset --mixed HEAD~1
# Or just:
git reset HEAD~1  # --mixed is default

# Files back to "Changes not staged"
# Use case: Want to reorganize which files go in which commit

Scenario 3: Completely Discard Last Commit

# ⚠️ DANGER: Loses all changes permanently
git reset --hard HEAD~1

# Use case: Commit was completely wrong, start over
# Make sure you REALLY want to delete the work!

Understanding the Difference:

# HEAD~1 means "one commit before HEAD"
# HEAD~2 = two commits back
# HEAD~5 = five commits back

# --soft: Move HEAD, keep staging area, keep working directory
# --mixed: Move HEAD, clear staging area, keep working directory  
# --hard: Move HEAD, clear staging area, clear working directory

# Visual:
# Before: A -- B -- C (HEAD)
# After reset --soft HEAD~1:
#         A -- B (HEAD)
#         Changes from C are staged

# After reset --mixed HEAD~1:
#         A -- B (HEAD)
#         Changes from C are unstaged

# After reset --hard HEAD~1:
#         A -- B (HEAD)
#         Changes from C are GONE

If You Already Pushed:

# ❌ Don't use reset if commit is pushed (rewrites history)

# ✅ Use revert instead (creates new commit that undoes changes)
git revert HEAD

# This creates a new commit that reverses the last commit
# Safe for shared branches because history isn't changed

# Example:
# Before: A -- B -- C (bad commit)
# After revert:
#         A -- B -- C -- C' (C' undoes C)
# Everyone can pull safely

Undo Multiple Commits:

# Undo last 3 commits, keep changes
git reset --soft HEAD~3

# Undo commits back to specific commit
git reset --soft abc1234

# See what commits would be undone
git log --oneline -5
# Pick the commit hash you want to reset TO

Committed to Wrong Branch:

# Oh no! Committed to main instead of feature branch

# 1. Create new branch at current commit
git branch feature-branch

# 2. Reset main to before your commits
git reset --hard origin/main

# 3. Switch to new branch
git checkout feature-branch

# Now your commits are on feature-branch, main is clean!

Recover After Accidental Hard Reset:

# You did: git reset --hard HEAD~5
# And lost commits you wanted!

# Find lost commits in reflog
git reflog

# Output shows:
# abc1234 HEAD@{0}: reset: moving to HEAD~5
# def5678 HEAD@{1}: commit: Important work  ← This is your lost commit!

# Restore it
git checkout def5678
# Or create branch from it
git branch recovered-work def5678

# Or reset back to it
git reset --hard def5678

Interactive Rebase – Advanced Undo:

# Edit/delete/reorder last 3 commits
git rebase -i HEAD~3

# Editor opens:
pick abc1234 Commit message 1
pick def5678 Commit message 2  
pick ghi9012 Commit message 3

# Options:
# pick = keep commit
# reword = change commit message
# edit = amend commit
# squash = combine with previous commit
# drop = delete commit

# Example - delete middle commit:
pick abc1234 Commit message 1
drop def5678 Commit message 2  ← Delete this
pick ghi9012 Commit message 3

# Save and close

Amend Last Commit (Different Use Case):

# Just want to modify last commit (message or files)
git commit --amend

# Add forgotten file to last commit:
git add forgotten-file.js
git commit --amend --no-edit  # Keep same message

# Change commit message only:
git commit --amend -m "New message"

Safety Best Practices:

# Before any destructive operation:
# 1. Check current state
git status
git log --oneline -10

# 2. Create backup branch
git branch backup-$(date +%Y%m%d-%H%M%S)

# 3. Now safe to experiment
git reset --hard HEAD~3

# 4. If something goes wrong:
git checkout backup-2024-02-03-143022

# Reflog is your safety net (keeps 90 days of history)
git reflog  # See all HEAD movements

Related posts:

Git: Switching Branches Without Committing Half-Done Work

Git: Keep History Clean with Squash Rebase

Git History Becomes Impossible to Understand

Post Views: 3

Post navigation

AJAX: Use Axios Interceptors to Automatically Retry Failed Requests
Git: Find and Remove Sensitive Data from Entire Repository History

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

March 2026
M T W T F S S
 1
2345678
9101112131415
16171819202122
23242526272829
3031  
« Feb    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (938)
  • How to add default value for Entity Framework migrations for DateTime and Bool (836)
  • Get the First and Last Word from a String or Sentence in SQL (828)
  • How to select distinct rows in a datatable in C# (801)
  • How to make theater mode the default for Youtube (736)
  • Add Constraint to SQL Table to ensure email contains @ (575)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (554)
  • Average of all values in a column that are not zero in SQL (523)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (477)
  • Find numbers with more than two decimal places in SQL (441)

Recent Posts

  • C#: Saving Memory with yield return (Lazy Streams)
  • C#: Why Records are Better Than Classes for Data DTOs
  • C#: Creating Strings Without Memory Pressure with String.Create
  • SQL: Protecting Sensitive Data with Dynamic Data Masking
  • SQL: Writing Readable Queries with Common Table Expressions (CTE)
  • .NET Core: Handling Errors Gracefully with Middleware
  • .NET Core: Mastering Service Lifetimes (A Visual Guide)
  • Git: Surgical Stashing – Don’t Save Everything!
  • Git: Writing Commits That Your Future Self Won’t Hate
  • Ajax: Improving Perceived Speed with Skeleton Screens

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (938)
  • How to add default value for Entity Framework migrations for DateTime and Bool (836)
  • Get the First and Last Word from a String or Sentence in SQL (828)
  • How to select distinct rows in a datatable in C# (801)
  • How to make theater mode the default for Youtube (736)

Recent Posts

  • C#: Saving Memory with yield return (Lazy Streams)
  • C#: Why Records are Better Than Classes for Data DTOs
  • C#: Creating Strings Without Memory Pressure with String.Create
  • SQL: Protecting Sensitive Data with Dynamic Data Masking
  • SQL: Writing Readable Queries with Common Table Expressions (CTE)

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com