To delete a commit in git, you're almost always doing one of two very different jobs: undoing your last commit, or reaching into the middle of your history to remove one commit that already has other work sitting on top of it. The first case is short, and it's already covered end to end in the guide to undoing your last commit. This one is about the harder case — the one most tutorials wave at and move past: dropping a specific commit from the middle of a branch, deleting a whole range of local commits at once, removing a commit that's already on GitHub, and, when a commit holds something that can never be left recoverable, actually erasing it for good.
Whatever you type into the terminal looking for this — git drop commit, git remove a commit, git
discard commit, or, understandably, git rm commit or git strip commit, since both sound like they
should work — the real toolkit is small: interactive rebase's drop action, git
rebase --onto, and for anything already shared with other people, a revert or a coordinated
history rewrite. The sections below walk through each one, in the order you'll actually need them.
Quick Answer: Which Command Deletes Which Commit
How to delete a commit in git depends entirely on where that commit sits and who else has seen it. The short version, before the detail below:
- Last commit, never pushed —
git reset --hard HEAD~1(or--soft/--mixedto keep the changes around). - One commit buried in the middle, never pushed — interactive rebase, mark that
one line
drop. - Five, ten, or more commits in a row, never pushed — interactive rebase again, or
git rebase --ontoto skip the whole range in one command. - Already pushed and shared — don't rewrite and force-push blindly. Either
git revertit, which adds a new commit undoing the change and leaves history intact, or drop it locally and force-push with--force-with-leaseafter telling every collaborator. - Committed a secret — none of the above is enough. Rotate the credential, then
rewrite history with
git filter-repo.
# The general-purpose way to drop one or more commits
git rebase -i HEAD~5
# In the editor that opens, change "pick" to "drop" for whatever
# commit(s) you want gone, then save and close it
That single command — an interactive rebase with a line changed from pick to
drop — answers most of what people mean by git remove a commit, git delete local commits, or how to
delete a commit. The rest of this guide is the detail behind it: when it's safe, when it conflicts, when a
non-interactive shortcut is faster, and what to do once a commit has already left your machine.
Local or Pushed? The Question That Decides Everything
Git never really "deletes" a commit in the sense of shredding data the instant you ask. Every
method in this guide — reset, rebase, revert, even filter-repo — works by moving
pointers: a branch stops referencing a commit, and that commit becomes unreachable until git's
garbage collector eventually cleans it up. That's true whether you call the operation git drop
commit, git discard commit, or git erase commit. What actually changes the risk level is a single
question: has anyone else already fetched this commit?
If a commit exists only on your machine — never pushed, or pushed to a branch nobody else has pulled from — every rewriting command below is safe to run without warning anyone, because nobody else has a copy of the old history to conflict with. If the commit is already on a shared remote branch that a teammate has fetched, rewriting your local history and force-pushing moves the branch out from under them, and that's the collaboration hazard most quick answers skip entirely — covered in full once GitHub enters the picture, further down.
Checking which side of the line a commit falls on takes one command: compare your branch against its upstream and see what hasn't gone out yet.
# Commits that exist locally but haven't reached origin/main
git log origin/main..HEAD --oneline If the commit you want gone shows up in that list, it's local-only and every technique in this guide applies without any coordination step. If it doesn't show up, it's already shared, and you're in the territory covered in removing a commit that's already on GitHub.
Deleting the Last Commit: The Simple Case
If the commit you want gone is the tip of the branch — HEAD itself — you don't need
anything below this section. git reset --hard HEAD~1 deletes the last commit and
discards its changes outright; git reset --soft HEAD~1 deletes the commit but leaves
its changes staged; git commit --amend replaces it with a corrected version instead of
removing it entirely.
# Delete the last commit and its changes completely
git reset --hard HEAD~1
# Delete the last commit, keep its changes staged for a new one
git reset --soft HEAD~1
# Replace the last commit with a corrected version
git commit --amend
All three, plus the reflog recovery that's specific to a plain reset and the exact safety rules for
each mode, are covered start to finish in
the dedicated guide to undoing your last commit; if
you only need --amend's behavior in detail — fixing a message, adding a forgotten file,
changing authorship — the commit amend guide covers that on
its own. Everything from here on assumes the commit you're after is not the tip — it has at
least one other commit sitting on top of it, which is what makes deleting it a different problem.
Delete a Specific Commit from the Middle with Interactive Rebase
Say a branch has five commits, oldest to newest: A, B, C,
D, E (E is HEAD), and C needs to
disappear without touching the order or content of the other four. Interactive rebase is the tool —
it's the direct answer to git drop commit, git remove specific commit, or git remove one commit,
whichever phrasing got you here.
# Open an editor listing every commit from five commits back to HEAD
git rebase -i HEAD~5 Git opens a todo list, oldest commit first, one line per commit:
pick 1a2b3c4 Add retry logic to api client
pick 2b3c4d5 Add debug console.logs
pick 3c4d5e6 Fix login validation
pick 4d5e6f7 Bump dependency versions
pick 5e6f7a8 Add integration tests
Change pick to drop on the line for the commit you want gone — or delete
that line outright, which has the identical effect:
pick 1a2b3c4 Add retry logic to api client
drop 2b3c4d5 Add debug console.logs
pick 3c4d5e6 Fix login validation
pick 4d5e6f7 Bump dependency versions
pick 5e6f7a8 Add integration tests Save and close the editor. Git replays every remaining commit, in order, on top of what's left; the dropped commit's changes vanish from the branch as though they'd never been made — unless a later commit's diff depends on content that only the dropped commit introduced, in which case the replay pauses with a conflict, covered in the conflicts section below.
A related but different question worth answering here: how do you delete a file from a
commit, rather than the whole commit? If it's the last commit, git rm --cached <file>
followed by git commit --amend handles it in one step — see
the amend guide for the full mechanics. If the file was
introduced several commits back, mark that commit's line edit instead of drop
in the same interactive rebase todo list, run git rm <file> and git commit
--amend, then git rebase --continue to let the rest of the series replay
normally.
git rm commit and git strip commit: The Commands That Don't Exist
Two things people type expecting them to delete a commit, that will instead throw an error or do something else entirely — worth addressing directly rather than skating past.
git rm commit isn't a thing git recognizes as a unit. git rm removes
files from the working tree and the index, full stop — it has no concept of a "commit" as its
target. Run git rm src/config.ts and you've staged the deletion of a file, not a
commit; you'd still need a separate git commit to record that deletion, and the file's
earlier history stays exactly where it was. If what you actually want is a file gone from one
specific past commit, that's the edit-mode rebase from the section above, not
git rm used alone.
git strip is a real command — just not git's. It belongs to Mercurial
(hg strip), where it permanently removes a changeset and its descendants from history —
genuinely close to what "git strip commit" sounds like it should do. Git has no equivalent verb by
that name; the closest git gets is exactly the interactive-rebase drop and
git filter-repo operations covered in this guide. If git strip doesn't run
on your machine, that's not a missing flag or a typo — the command isn't part of git at all.
Deleting a Range of Local Commits
Dropping five or ten commits in one pass — git delete local commits, in the phrasing people search for — uses the same interactive rebase, just with more lines changed at once.
# See the last eight commits with short hashes
git log --oneline -8
# Open the interactive editor for the same range
git rebase -i HEAD~8
# Delete the lines for every commit you want gone, or change
# "pick" to "drop" on each one — save and exit
Two shortcuts are worth knowing before you open an editor for a big range. If every commit you're
removing is contiguous and sits at the very end of your branch — everything since you branched off
main, say — moving the branch pointer directly is faster than editing a todo list line by line:
git reset --hard <last-good-commit> throws away every commit after that point in
one step. That command, and every other git reset mode, is covered in full generality in
the git reset guide; deleting local commits from the end of a
branch is just one of the scenarios it walks through. And if the goal for a range isn't removing it
but consolidating it — turning several messy commits into one clean one instead of deleting content —
that's squashing, not deletion; see
the squashing guide if that's actually what you're after.
For a range that sits in the middle of your history rather than at the tip — when you need to
remove commits that have other, wanted commits both before and after them — interactive rebase handles it the
same way as a single commit, just with more lines marked drop. There's also a
non-interactive way to do the exact same thing without opening an editor at all, which is worth its
own section next.
git rebase --onto: Surgical Removal Without an Editor
git rebase --onto takes three references — a new base, an upstream boundary, and
(optionally) a branch — and replays every commit that's reachable from the branch but not from the
upstream boundary, directly onto the new base — the argument order and exclusivity rules are
spelled out in git's
own rebase documentation. For deleting a commit, that means: point
--onto at the commit right before the one you want gone, and give the bad
commit itself as the boundary. Everything after it gets replayed onto the good commit, and the bad
one is simply never included.
Using the same five commits from earlier — A, B, C,
D, E — dropping just C looks like this:
# Replay everything after C onto B, skipping C entirely
git rebase --onto B C
Read it as "onto B, everything after C": the upstream argument is
exclusive, so C itself never gets replayed, while D and E land
on top of B as if C had never existed. The same command deletes a whole
contiguous range in one shot — to remove both B and C, keeping
A, D, and E, the boundary just moves to the last commit you
want excluded:
# Replay everything after C onto A, skipping both B and C
git rebase --onto A C
This is the most scriptable way to remove commits from a local branch — no editor,
no manually changing pick to drop on the right lines, just three commit
references. It's the same command that does the heavier job of moving an entire branch onto a
different base; the main rebase guide covers that broader use
of --onto plus rebase conflict handling and the golden rule for when not to rebase at
all, if you want the fuller picture beyond commit deletion specifically.
When Dropping a Commit Causes Conflicts
Dropping a commit isn't always clean. If a later commit's diff touches lines that only exist because of the commit you just removed — it modifies a function the dropped commit added, for instance — the replay has nothing to apply that change against, and the rebase stops mid-sequence:
Auto-merging src/config.ts
CONFLICT (content): Merge conflict in src/config.ts
error: could not apply 3c4d5e6... Fix login validation Working through it is the same loop every time:
git statuslists exactly which file(s) are conflicted.- Open each one and look for the
<<<<<<</=======/>>>>>>>markers. Decide what the final content should be — usually this means re-applying, by hand, whatever part of the later commit's intent still makes sense without the code that got dropped. git add <file>once it's resolved.git rebase --continuemoves on to the next commit in the sequence. If the conflict resolution made a commit a no-op — its entire change already absorbed elsewhere —git rebase --skipdrops it from the series instead.- If the conflicts cascade further than expected and dropping this commit turns out to be the
wrong call,
git rebase --abortunwinds everything back to exactly where you started — nothing is lost.
This is normal, not a sign anything went wrong — it's git telling you, correctly, that the commit you dropped wasn't as independent from the rest of the branch as it looked. The deeper a commit sits under other work, the more likely later commits build on something it introduced.
How to Remove a Commit from GitHub After You've Pushed
GitHub has no separate "delete this commit" button, because a commit on GitHub is just a commit on a branch that happens to be hosted there. How to remove a commit from GitHub, how to delete a commit in GitHub, github discard commit, github drop commit — every version of that question resolves to the same two-step answer: drop the commit locally using any method above, then push the rewritten branch.
A plain git push gets rejected at that point, because the remote's history is no longer
a fast-forward of what you have locally — the whole point was to remove something the remote still
has. You need a force push:
# After dropping the commit locally with rebase or --onto
git push --force-with-lease origin feature-branch --force-with-lease checks that the remote branch still matches what you last fetched
before overwriting it, and refuses with an error if someone else has pushed to it since — plain
--force overwrites unconditionally, no matter what's sitting there. Prefer
--force-with-lease every time you're rewriting a branch that isn't purely yours. Note
also that most teams configure branch protection to block force-pushes to main or
master outright, so this realistically applies to feature branches, not the trunk.
Once you force-push, GitHub updates the branch and any open pull request to reflect the new history — the removed commit drops out of the PR's commit list. That's not the same as the commit being gone everywhere: it can still be reachable through GitHub's own object storage for a while, and it's certainly still sitting in any fork or local clone that already pulled it before you rewrote things. What "gone" actually means, and how to check for it, is covered fully in the recovery section below.
Does deleting a commit affect collaborators? Yes, directly, if they've already pulled the branch you
just rewrote. Their local copy still has the old history, so their next git pull either
fails outright as a non-fast-forward rejection, or — if their pull is configured to merge rather than
rebase — silently merges their old branch tip back in, quietly reintroducing the exact commit you
just removed. The step that's easy to skip: tell every collaborator before you force-push, and hand
them the exact command to resync instead of guessing:
# Replace local history with the rewritten remote, don't merge against it
git fetch origin
git reset --hard origin/feature-branch Deleting the Effect, Not the Commit: git revert
Every method above rewrites history — the commit's hash disappears, and anyone still holding the old
version has diverged from you. Sometimes that's the wrong tool entirely: on a branch where rewriting
isn't an option, or when you want an honest record that a change was made and then undone, rather
than making it look like it never happened, git revert <hash> is the better fit.
It creates a new commit whose diff is the exact inverse of the one you're targeting — the unwanted
change is gone from the working tree, the original commit stays untouched in history, and nobody has
to force-push or resync anything.
The short version of reset (and rebase-drop) versus revert: reset and rebase rewrite history and are only safe before a commit has been shared; revert adds new history and is safe at any point, shared or not. Full coverage of revert — including reverting a merge commit and reverting a revert — lives in the dedicated revert guide.
Erasing a Commit for Real: Secrets and git filter-repo
None of the methods above are secure-delete operations. Reset, rebase-drop, revert, even a force-push — every one of them is a branch-history operation, not a guarantee that content is gone forever. A dropped commit stays reachable through your own reflog for weeks by default, and if anyone else ever fetched it, they hold the full commit, tree, and blob objects indefinitely, regardless of what you do to your own branch afterward. If git erase commit is what you actually meant — the content gone, not merely unreferenced — this is the only section on the page that gets you there.
That distinction matters the moment a commit contains something that genuinely can't be left
recoverable — a real password, a private API key, an OAuth token, a leaked .env file.
Fixing it is two steps, and skipping either one leaves you exposed:
- Rotate or revoke the credential immediately. This matters more than any git command that follows — a leaked key stays dangerous regardless of what you do to history, since someone may have already copied it.
- Rewrite history everywhere the blob exists, using
git filter-repo— the community-maintained, git-project-recommended tool, and the direct successor togit filter-branch, which the git project itself now discourages for this because it's slow and easy to misuse.
# git-filter-repo is a separate install, not bundled with git
pip install git-filter-repo
# Remove a specific file from every commit in history
git filter-repo --path secrets/.env --invert-paths
# Or strip a specific string wherever it appears, using a
# replacements file (old-text==>new-text, one per line)
git filter-repo --replace-text replacements.txt filter-repo rewrites every commit downstream of the one containing the secret, which
means every hash after that point changes — disruptive to every open pull request, every fork, and
any CI job that pins a specific commit SHA. After running it, a force-push to every affected branch
is required, and every collaborator needs to re-clone or hard-reset to the rewritten history rather
than pull or merge normally, since a standard fetch won't reconcile histories that no longer share a
common ancestor at that point. For a public repository, GitHub's
own guidance also recommends contacting their support directly to purge cached views of the old commit from their own systems, since forks and
cached pages can retain the old blob independent of your rewritten remote.
Recovering a Commit You Deleted by Mistake
Because git orphans commits rather than destroying them immediately, a mistaken drop is usually
recoverable. The tool is git reflog — but it reads differently after an interactive
rebase than it does after a plain reset, and that difference trips people up.
After a plain git reset --hard, the reflog has one clean entry for the reset itself, and
the commit you want is typically sitting right at HEAD@{1}. After an interactive
rebase, the reflog is noisier: every commit that got replayed adds its own rebase (pick)
entry, plus a rebase (start) and rebase (finish) bookend, so the dropped
commit isn't sitting at one obvious offset — you're looking for its original hash, which never
changes during a rebase, somewhere in the run of entries from just before the rebase began.
$ git reflog
a1b2c3d (HEAD -> feature) rebase (finish): returning to refs/heads/feature
a1b2c3d rebase (pick): Add integration tests
9f8e7d6 rebase (pick): Fix login validation
5c4b3a2 rebase (start): checkout HEAD~5
7e6d5c4 commit: Add integration tests
6d5c4b3 commit: Fix login validation
2b3c4d5 commit: Add debug console.logs
1a2b3c4 commit: Add retry logic to api client 2b3c4d5 — the commit dropped in the earlier walkthrough — is right there, unlabeled as
anything special, sitting in the pre-rebase run of plain commit: entries. Once you've
spotted the hash, two ways to bring it back:
# Reapply just that commit's changes onto your current branch
git cherry-pick 2b3c4d5
# Or point a new branch at it directly, recovering it and anything
# that used to sit on top of it, exactly as it was before the rebase
git branch recovered-work 2b3c4d5 The cherry-pick option is the more common of the two, and it's the same underlying mechanism used for moving any commit across branches deliberately, not just recovering one — the cherry-pick guide covers that mechanism in full if this is your first time reaching for it.
Comparison Table: Every Deletion Method Side by Side
Every method from this guide, side by side, for picking the right one at a glance.
| Method | Rewrites History? | Safe When Already Pushed? | Keeps the Changes? | Difficulty |
|---|---|---|---|---|
git reset --hard HEAD~n | Yes | No — local-only branches only | No — changes discarded | Easy |
git reset --soft HEAD~n | Yes | No — local-only branches only | Yes — left staged | Easy |
Interactive rebase (drop) | Yes | Only with a coordinated force-push | No — dropped commit's changes gone | Moderate |
git rebase --onto | Yes | Only with a coordinated force-push | No — dropped range gone | Moderate to advanced |
git revert | No — adds a new commit | Yes — the safest option on shared branches | Effect removed via an inverse commit | Easy |
| Force-push after a rewrite | Publishes a rewrite already done | Only with --force-with-lease and warning | Depends on the rewrite performed | Moderate — coordination risk |
git filter-repo | Yes — every downstream hash changes | No — requires a full team re-clone | No — content is what's being removed | Advanced |
Read the Commit Before You Delete It
Before dropping a commit or force-pushing a rewritten branch, it's worth seeing exactly what's about to disappear, in a form easier to scan than scrolling raw terminal output.
Diff Checker — the free Chrome extension this site is built around, also usable directly at diffchecker.pro — has no git integration and doesn't know what a commit is; it's a Monaco-based editor that renders a live comparison between whatever text sits in its two editable panes, with a Split/Unified toggle and three compare methods (Smart Diff, Ignore Whitespace, and Classic LCS). That's exactly the right shape for two checks a terminal makes tedious.
Before dropping: paste the output of git show <hash> for the
commit you're about to drop into one pane, and the current version of the file it touches into the
other. Seeing them side by side makes it obvious whether anything later in the file still depends on
what that commit introduced — the exact condition that causes the conflicts covered earlier.
Before force-pushing: paste the output of git log --oneline from before
your rebase into one pane and the same command run after into the other. Show Diff Only, with the
context selector turned down to zero or one line, turns that into a short, readable list of exactly
which commits vanished and which hashes downstream changed — precisely the list a collaborator who
hasn't fetched yet is about to be surprised by.
Both workflows only ever need plain-text comparison — reading, not applying — which is what the tool does and all it claims to do. Comparisons and local history (the 50 most recent, stored in IndexedDB) never leave the machine, which is a reasonable thing to want when the text in question is a diff you haven't decided is safe yet.
Frequently Asked Questions
How do I delete a specific commit from history?
Use interactive rebase: run git rebase -i with a range that includes the commit
before your target, find that commit's line, and change pick to drop
(deleting the line entirely works the same way). Save and exit, and git replays every remaining
commit in order while the dropped one's changes disappear. This is safe on local-only commits;
on a shared branch you'll need to force-push and coordinate with collaborators afterward.
Can I undo a deleted commit?
Usually, yes. Dropping a commit through reset, rebase, or even a force-push doesn't destroy the
commit object right away — it just stops any branch from pointing at it. The commit stays in
your local repository, findable through git reflog, until git's garbage collector
eventually prunes unreachable objects (by default, after roughly 30 days). Find its hash in the
reflog, then git cherry-pick it or point a new branch at it to bring it back.
Can I delete a commit that's already pushed?
Yes, but it requires a force-push and carries more risk than deleting a local-only commit. Drop
the commit locally with interactive rebase, git rebase --onto, or git reset
--hard, then push the rewritten branch with git push --force-with-lease,
which refuses to overwrite the remote if someone else pushed to it since your last fetch. Many
repositories also block force-pushes to protected branches like main, so this
realistically applies to feature branches.
Does deleting a commit affect collaborators?
Yes, if they've already pulled the branch. Their local copy still holds the old history, so their
next pull either fails as a non-fast-forward rejection or, if they merge instead of rebasing,
silently reintroduces the commit you just removed. Tell collaborators before force-pushing a
rewritten branch, and give them the fix: git fetch followed by git reset
--hard against the remote branch replaces their local history instead of merging against
it.
How do I remove sensitive data from Git history?
Rotate or revoke the exposed credential first — that matters more than any git command, since a
leaked key stays dangerous regardless of what happens to your repository. Then use git
filter-repo, git's recommended tool (not the deprecated git filter-branch),
to strip the file or string from every commit in history, force-push the result to every
affected branch, and have every collaborator re-clone rather than pull.