Git Undo Commit Keep Changes A Practical Guide

Git Undo Commit Keep Changes A Practical Guide

It’s a feeling every developer knows. You finally push that commit, only to spot a stray debug statement, a forgotten file, or realize you just weren't quite ready. That initial flash of panic is real, but thankfully, Git has our backs with powerful, safe ways to fix these little slip-ups without losing a single line of code.

Knowing how to undo a commit while keeping your work is a fundamental skill. It’s all about controlling your local workflow and maintaining a clean, logical project history before sharing your code with the team.

This isn't just about fixing typos. Messy or premature commits can snarl collaboration and make the project's history a nightmare to navigate. The goal here is simple: rewind the commit itself—that snapshot in time—but leave all your actual file modifications right where they are.

This gives you the flexibility to:

  • Amend the commit: Add new changes to the previous commit without creating a second one.
  • Split the commit: Break a massive commit into smaller, more logical chunks.
  • Re-commit correctly: Tidy things up and write a new, much cleaner commit message.

The most direct way to get this done is with the command git reset --soft HEAD~1. This command moves the HEAD pointer back by one commit, effectively erasing it from your local history, but it keeps all your code changes staged and ready to be re-committed. Your work stays exactly as it was.

It’s an incredibly common scenario. In fact, some studies suggest that around 70% of developers accidentally commit incomplete work or mistakes at least once per project cycle. You can learn more about Git best practices and methods on Aviator.co.

To help you choose the right tool for the job, here's a quick rundown of the main commands for undoing commits while keeping your changes.

Quick Guide to Undoing Git Commits and Keeping Changes

This table provides a quick comparison of the main commands for undoing commits while preserving your work, highlighting their primary use case and impact on your Git history.

Command Primary Use Case Impact on History Safety Level
git reset --soft Undo local commits, keep changes staged Rewrites local history Safe for local
git reset --mixed Undo local commits, unstage changes Rewrites local history Safe for local
git revert <hash> Undo a pushed commit on a shared branch Adds a new commit Safe for shared

Each of these commands serves a different purpose, especially when you consider whether the commit lives only on your machine or has already been pushed to a shared branch. Let's dig into how each one works.

Mastering git reset --soft for Your Local History

When you need to undo a commit but hang on to the actual work you did, git reset --soft is your best friend. This is especially true for commits that haven't been pushed out to a shared repository yet.

Think of it as a local time machine. It rewinds your commit history but carefully leaves your current work untouched in the staging area. It simply moves the branch pointer back, without messing with your files.

This command really comes in handy when you've made a few quick commits that, in hindsight, would look much better as a single, well-crafted update. Maybe you committed a fix, then spotted a typo and made another commit, then added a comment in a third. Instead of cluttering up the project history, you can just rewind and bundle them together.

Image

Combining Multiple Local Commits

Let's walk through a super common scenario. Say you have three recent commits on your local branch: fix: typo, feat: add button, and style: refactor css. You decide it makes more sense to merge these into one clean commit. No problem. You can undo those last three commits while keeping all the code changes staged and ready for a new commit.

To pull this off, you just run:
git reset --soft HEAD~3

This command tells Git to move the HEAD—the pointer to your current commit—three steps back. Once you run it, your commit history is tidier. Crucially, every single change from those three commits is now sitting in your staging area, waiting for your next move.

Using git reset --soft doesn't delete your work; it just rewinds the commit history. Your code stays staged, giving you total control to either amend an older commit or craft an entirely new one.

This technique is a staple in professional workflows. In fact, some reports on developer tool usage suggest this method is used in around 40% of mid-to-large software projects to help keep the codebase clean and understandable. For a deeper dive, check out this guide on how developers uncommit changes at KodeKloud.

Verifying the Changes

After any reset, it's always a good idea to double-check that everything is how you expect it. A quick git status will confirm your changes are safe. You should see all the files from the commits you just "undid" listed in green, under "Changes to be committed."

From here, you've got a couple of options:

  • Create a new, single commit: Run git commit -m "feat: implement new feature with styling" to bundle everything into one comprehensive update.
  • Amend a previous commit: If you want to add these changes to the commit that came before the ones you just reset, you can use git commit --amend.

Getting comfortable with git reset --soft gives you the power to polish your local history with confidence before you share it with the rest of the team.

Safely Undoing Pushed Commits with Git Revert

Once you’ve pushed a commit to a shared branch, git reset suddenly becomes a very dangerous game. Rewriting the history that your teammates have already pulled down is a recipe for infuriating conflicts and a tangled mess.

This is exactly where git revert comes in. It's the collaboration-friendly way to undo a commit while keeping everyone’s work intact.

Instead of just deleting a commit from the project's history, git revert creates a brand-new commit that does the exact opposite of the one you want to fix. If the original commit added a line of code, the revert commit will remove it. This approach keeps the project history clean, linear, and fully auditable—which is non-negotiable for team transparency.

Image

That distinction is critical in any distributed team working on the same branches. In fact, in a recent survey of over 3,000 developers, 65% preferred using git revert for undoing pushed commits precisely because it maintains a safer, more transparent history.

How to Revert a Specific Commit

The process itself is surprisingly straightforward. First, you need to find the unique identifier, or hash, of the commit you want to reverse. Just run git log to find it.

Once you have the hash (let's say it's a1b2c3d), you simply run this command:

git revert a1b2c3d

Git will then pop open your default text editor, asking you to confirm the commit message for this new revert commit. The default message is usually perfect, something like "Revert 'feat: add new login button'".

Just save and close the editor, and the new commit is created. You can now safely push this to the shared branch, confident you haven't disrupted anyone else's work. For a deeper dive, check out our guide on how to properly revert a commit after push.

By creating a new commit that reverses the changes, git revert safely undoes mistakes on shared branches without rewriting history. This makes it the go-to command for fixing errors in a collaborative environment.

One thing to watch out for: reverting a commit—especially a merge commit—can sometimes cause conflicts if other changes have been made to the same files. If that happens, Git will pause the process and ask you to resolve the conflicts manually, just like you would during a regular merge. Once you've sorted it out, you can continue with the revert.

Picking the Right Tool: soft vs. mixed vs. hard Reset

To really get the hang of how you git undo commit keep changes, you first need to understand the big differences between the git reset modes. Each one offers a different level of "undo," and knowing which to use is the key to not accidentally blowing away your work.

The git reset command really only cares about three parts of your repository:

  • HEAD: This is just a pointer to the last commit on your current branch.
  • Staging Area (Index): Think of this as the waiting room where changes hang out before they get committed.
  • Working Directory: These are the actual files on your machine that you're actively editing.

Each reset mode plays with these three areas a little differently, giving you some pretty precise control over your project's state.

Git Reset Mode Comparison

To make it crystal clear, let's break down exactly what each reset mode does to your commit history, staging area, and working directory.

Reset Mode Commit History (HEAD) Staging Area (Index) Working Directory
--soft Moves pointer back Unchanged Unchanged
--mixed Moves pointer back Resets (unstages changes) Unchanged
--hard Moves pointer back Resets (clears it) Resets (discards changes)

As you can see, the main difference lies in what happens to your staged and local changes. --soft is the safest, while --hard is the one you need to be careful with.

Soft Reset: Just Rewind the Commit

A --soft reset is the most gentle option of the bunch. All it does is move the HEAD pointer back, leaving your staging area and working directory completely alone.

This means all the changes from the commit you just undid are still staged and ready for a new commit. It's the perfect tool for when you just want to rewrite a commit message or squash a few recent commits together into one.

Mixed Reset: Unstage the Changes

Next up is --mixed, which happens to be Git's default behavior if you don't specify a mode. Like a soft reset, it moves the HEAD back. But it also clears out your staging area.

Your changes are perfectly safe in your working directory, but they're no longer staged. This is super useful when you've made a messy commit and want to undo it, then carefully re-stage only the specific files you want for a much cleaner follow-up commit.

A good way to remember it: --soft leaves your changes on the "staged" doorstep, ready to go. --mixed brings them back inside your house (working directory), and you have to decide what to do next. --hard throws them out entirely.

Hard Reset: The "Nuke It All" Option

Finally, there’s --hard. This mode is powerful and, frankly, pretty destructive if you're not careful. It moves the HEAD pointer, clears the staging area, and completely overwrites your working directory to match the commit you reset to.

When you run this, all your uncommitted changes are gone. For good. You should only use this with extreme caution, usually when you're 100% certain you want to scrap everything and start fresh from a previous commit. If you're curious about how this interacts with remote branches, you can learn more about what happens when you reset to a remote HEAD on Mergify's blog.

This image shows a much safer alternative, git revert, which is better for shared branches because it doesn't rewrite history.

Image

The key takeaway here is that reverting creates a new commit that undoes the changes, keeping the project's history intact instead of erasing it.

Emergency Recovery With Git Reflog

We’ve all been there. That heart-sinking moment when you realize you’ve just run git reset --hard and wiped out hours of work that you hadn't pushed yet. It feels catastrophic. But this is where git reflog comes in as your ultimate safety net, proving it’s almost impossible to truly lose work in Git.

Think of the reflog as a private, local history of every single move your HEAD pointer makes. Every time you switch branches, commit, or even perform a destructive reset, reflog is there, quietly taking notes. This log is your personal timeline, allowing you to jump back to a previous state—even one you thought was gone forever.

Image

Recovering From a Disastrous Reset

Let's walk through a real-world recovery. After that accidental hard reset, the first thing you need to do is pop open your terminal and run git reflog. You'll get a list of all your recent actions, each with a handy reference like HEAD@{1}.

Scan that list for the entry right before your mistake. It'll probably be labeled as a commit or a checkout. All you need to do is copy its commit hash.

Now for the magic. To restore your repository to that exact safe point, just run this command:
git reset --hard <commit_hash_from_reflog>

Just like that, your "lost" commits and changes are instantly back. What felt like a disaster a minute ago is now just a minor hiccup.

The git reflog is your personal undo history for your repository's HEAD. It allows you to recover from nearly any local mistake, including destructive operations like a hard reset.

For a deeper dive, the Mergify blog has a great guide on using Git reflog to find a lost commit. Getting comfortable with this command gives you incredible peace of mind, knowing there’s almost always a way to turn back the clock.

A Few Common "What Ifs" When Undoing Commits

Even once you have the basic commands down, real-world Git scenarios can throw some curveballs. Let's walk through a few of the most common questions that pop up when you need to undo a commit but hang on to your code.

How Do I Undo Changes to Just One File?

This happens all the time. You make a great commit, but you realize you accidentally included a change in one file that shouldn't have been there. You don't want to blow away the whole commit, just fix that one file.

You can do this with a targeted checkout command. Think of it like reaching back in time to grab a specific version of a file. Just run git checkout <commit_hash> -- <file_path>. This command yanks the older version of the file from that commit and drops it right into your staging area. From there, you can commit this "fix," effectively reverting the change to that single file while leaving everything else from the original commit alone.

Can I Safely Reset a Pushed Branch?

I'm going to be blunt: you really shouldn't. Unless you are 100% certain you are the only person who will ever touch that branch, using git reset on a shared branch is a recipe for disaster.

When you reset and then force-push, you're rewriting history. For any teammate who has pulled the old version of that branch, their local history now conflicts with the new history you just pushed. This creates a messy, confusing situation that's a nightmare to untangle.

The golden rule for shared branches is to always prefer git revert. It’s the safe, non-destructive way to undo changes. It creates a new commit that reverses the old one, keeping a clean and accurate project history for everyone on the team.

What if I Mess Up and Want the Reset Commit Back?

We've all been there. A moment of panic after a git reset goes wrong, and you think you've lost your work forever. This is exactly what the git reflog is for. It's your local Git safety net.

Just run git reflog in your terminal. You'll see a detailed log of every time your HEAD has moved—every commit, every reset, every checkout. Find the hash of the commit you accidentally nuked, and you can bring it right back with git reset --hard <commit_hash>. It’s the ultimate undo button for your local Git workflow.

Read more