Git
Version Control
Coding
Programming Tips
Commit Deletion

How do I delete unpushed git commits?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Use git reset --hard HEAD~N to delete the last N unpushed commits and discard all changes, or git reset --soft HEAD~N to undo the commits while keeping your changes staged for a new commit. Since these commits have not been pushed, you can safely rewrite local history without affecting anyone else.

Understanding What "Unpushed" Means

An unpushed commit exists in your local repository but has not been sent to the remote (e.g., GitHub, GitLab). You can see how many unpushed commits you have:

bash
1# Show commits that exist locally but not on the remote
2git log origin/main..HEAD --oneline
3
4# Or check the status summary
5git status
6# "Your branch is ahead of 'origin/main' by 3 commits."

Because these commits only exist on your machine, you can modify or delete them freely without affecting collaborators.

Method 1: git reset (Rewrite History)

git reset moves the branch pointer backward, effectively removing commits from the branch history. The three modes control what happens to the file changes from those commits:

Soft Reset: Keep Changes Staged

bash
# Undo the last 3 commits, keep all changes in the staging area
git reset --soft HEAD~3

After this, your working directory is unchanged and all modifications from the removed commits are staged. You can create a new, single commit with all those changes:

bash
git commit -m "Combined changes from previous three commits"

This is useful for squashing multiple small commits into one clean commit before pushing.

Mixed Reset (Default): Keep Changes Unstaged

bash
# Undo the last 2 commits, keep changes in working directory but unstaged
git reset HEAD~2
# Same as: git reset --mixed HEAD~2

The changes are preserved in your working directory, but they are not staged. You can review, selectively stage, and recommit:

bash
git diff                  # Review what changed
git add specific-file.py  # Stage only what you want
git commit -m "Cleaned up implementation"

Hard Reset: Discard Everything

bash
# Undo the last 2 commits AND throw away all changes
git reset --hard HEAD~2

This is destructive. The commits are removed, and all file modifications from those commits are permanently deleted from your working directory. Use this when you want to completely abandon the work in those commits.

Method 2: git revert (Preserve History)

If you want to undo changes from specific commits without rewriting history, git revert creates new commits that reverse the effect:

bash
1# Revert the most recent commit
2git revert HEAD
3
4# Revert a specific commit by hash
5git revert a1b2c3d
6
7# Revert multiple commits (oldest to newest order)
8git revert HEAD~3..HEAD

git revert is primarily designed for undoing pushed commits, but it works for unpushed commits too. The key difference from git reset is that the original commits remain in the history.

Method 3: Reset to a Specific Commit

Instead of counting backward with HEAD~N, you can reset to a specific commit hash:

bash
1# Find the commit you want to go back to
2git log --oneline
3# Output:
4# f4d5e6a Fix typo in README
5# c3b2a1d Add user authentication     <-- keep everything up to here
6# b2a1c3d Initial project setup
7
8# Reset to that specific commit
9git reset --hard c3b2a1d

This removes every commit after c3b2a1d, regardless of how many there are.

Method 4: Interactive Rebase (Selective Removal)

When you need to remove specific commits from the middle of your unpushed history while keeping others, use interactive rebase:

bash
# Rebase the last 5 commits interactively
git rebase -i HEAD~5

This opens an editor showing:

 
1pick a1b2c3d Add login page
2pick b2c3d4e Fix login validation
3pick c3d4e5f Add temporary debug logging    <-- delete this line
4pick d4e5f6g Add logout feature
5pick e5f6g7h Update tests

Delete the line for the commit you want to remove (or change pick to drop), save and close the editor. Git replays the remaining commits without the removed one.

Comparison of Methods

MethodChanges to FilesHistoryBest For
git reset --soft HEAD~NKept (staged)Commits removedSquashing commits before push
git reset --mixed HEAD~NKept (unstaged)Commits removedRe-organizing changes into new commits
git reset --hard HEAD~NDiscardedCommits removedCompletely abandoning work
git revert HEADReversed by new commitPreserved + new commitUndoing pushed commits (or audit trail)
git rebase -i HEAD~NDepends on actionRewrittenRemoving specific commits from middle

Recovering from an Accidental Hard Reset

If you run git reset --hard and immediately realize you lost work you needed, Git's reflog saves you:

bash
1# Show recent HEAD movements
2git reflog
3# Output:
4# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
5# f4d5e6a HEAD@{1}: commit: Important work I need back
6# c3b2a1d HEAD@{2}: commit: Another commit
7
8# Restore to the state before the reset
9git reset --hard HEAD@{1}

The reflog keeps entries for about 90 days by default, so you have a window to recover.

Resetting to Match the Remote Exactly

If you want to throw away all local commits and make your branch identical to the remote:

bash
1# Fetch the latest remote state
2git fetch origin
3
4# Reset local branch to match remote exactly
5git reset --hard origin/main

This discards all unpushed commits and any uncommitted changes. Your local branch becomes an exact copy of the remote branch.

Common Pitfalls

Using git reset --hard on pushed commits. If commits have already been pushed to a shared branch, git reset --hard followed by git push --force rewrites public history. This breaks the repository for everyone who has pulled those commits. Use git revert for pushed commits instead.

Forgetting that git reset without a flag defaults to --mixed. Running git reset HEAD~2 keeps your changes but unstages them. If you expected either a soft or hard reset, the result will surprise you. Always specify the flag explicitly.

Counting commits wrong with HEAD~N. HEAD~1 means one commit before HEAD (removing only the latest commit). HEAD~2 means two commits before HEAD (removing the last two). Off-by-one errors are common. Use git log --oneline to verify commit hashes before resetting.

Losing untracked files. git reset --hard does not delete untracked files (files that were never added to Git). If you also need to clean up untracked files, run git clean -fd separately. But be careful since this deletion is permanent and not recoverable via reflog.

Interactive rebase conflicts. When you remove a commit via git rebase -i that other commits depend on, Git will stop with a merge conflict. You will need to resolve the conflict manually and run git rebase --continue.

Summary

For unpushed commits, git reset is the most direct tool. Use --soft to keep changes staged for recommitting, --mixed (default) to keep changes unstaged for review, or --hard to discard everything. For selective removal from the middle of your history, use interactive rebase. Reserve git revert for pushed commits where history preservation matters. Always check git reflog if you accidentally discard work you need, as Git keeps a recovery log for about 90 days.


Course illustration
Course illustration

All Rights Reserved.