GitHub

How to Delete a Commit in GitHub

How to Delete a Commit in GitHub

GitHub’s web interface can’t delete a commit. The closest thing is a Revert button on merged pull requests, and it adds an inverse commit instead of removing the original. Real deletion happens in a local clone. You rewrite the branch history with git reset or an interactive rebase, then overwrite the remote branch with a force push.

Which command you reach for depends on whether the commit is already pushed and whether teammates have pulled it.

A force push doesn’t finish the job when the commit holds a secret. According to GitHub Docs (2026), the old commit stays reachable through clones, forks, cached views addressed by SHA, and pull request references.

Can You Delete a Commit From GitHub’s Web Interface?

No. The removal happens in a local clone and gets pushed afterward.

Some tutorials describe a “Delete” button at the bottom of the commit page. It isn’t there.

Git performs the rewrite and GitHub only hosts the result, a split explained in this breakdown of how Git and GitHub differ.

What GitHub does offer is the Revert button on merged pull requests, and you need write permission on the repository to use it. The output is a new pull request, not a removed commit. GitHub Docs (2026) describe it as a pull request that reverses the original merge commit. It adds history. It never takes any away.

If the goal is removal, the commands in the next sections are the only route.

Which Method Fits Your Commit?

Whether the commit is pushed, and whether anyone else has pulled it, settles most of the decision.

  • An unpushed commit at the tip calls for git reset, and nobody else notices.
  • A commit buried in your own branch is a job for interactive rebase with drop.
  • Once the commit sits on a shared branch, git revert is the safer pick because it adds an inverse commit instead of rewriting anything.
  • If the commit contains a secret, only git filter-repo does enough. Deleting from one branch leaves the data in other places.
MethodRewrites historyWorks on pushed commitsCollaborator impact
git resetYesOnly with a force pushEach teammate repairs a local copy
git rebase -iYesOnly with a force pushSame, plus new SHAs for every later commit
git revertNoYes, plain pushNone
git filter-repoYes, across all refsYes, with force pushesHighest

Git’s own manuals agree on the risk. The reset page advises against hard-resetting commits that other people already have, and the rebase page says anyone downstream of a rewritten branch has to fix their history by hand (Git documentation, pages last revised in version 2.54).

Why is GitHub the heart of open source?

Uncover GitHub statistics: developer community growth, repository trends, collaboration patterns, and the platform that powers modern software development.

Explore GitHub Data →

On a shared main branch, revert beats delete almost every time. Rewriting only makes sense on a branch you own, or when a secret has to disappear.

How to Delete the Last Commit With git reset

YouTube player

Run git reset HEAD~1 to remove the latest commit from the branch. The mode flag decides what happens to its changes. --soft keeps them staged, --mixed (the default) leaves them unstaged, and --hard discards them.

  1. Run git log --oneline -3 and confirm the top entry is the commit to remove.
  2. Choose a mode: soft, mixed or hard.
  3. Run git reset --soft HEAD~1, swapping in --mixed or --hard as needed.
  4. Run git status and git log --oneline -3 again to check the result.
  5. If the commit was already pushed, follow the push steps further down.

The way git reset moves the branch pointer is the whole trick. The old commit stays in the object database until Git cleans it up.

Keep the changes (soft and mixed)

With a soft reset the pointer moves and the index and working tree stay put, so the changes sit staged and ready to commit again. A mixed reset moves the pointer and resets the index too, which leaves the changes in your files but unstaged.

Soft fits a commit with the right content and the wrong message. Mixed fits a commit you want to split in two, which is the workflow Git’s manual demonstrates with git add -p.

Looking to sharpen your Git skills? Branching, merging, rebasing, and everything else you need - including git rebase, git stash, and commit workflows - is on one page in the Git Cheat Sheet.

Discard the changes (hard)

A hard reset rewrites the working tree to match the target commit, and Git’s manual notes it may overwrite untracked files too.

Uncommitted edits vanish for good. Tracked files that don’t exist in the target get removed, and untracked files in the way can be overwritten.

Uncommitted work never enters the reflog, so a hard reset over it cannot be rolled back that way. Commit or stash first when in doubt.

How to Delete a Commit From the Middle of History

Start an interactive rebase from the parent of the unwanted commit, then change pick to drop on its line. Git replays every later commit on top of the parent and leaves the dropped one out.

  1. Run git log --oneline and note the hash of the commit to remove and of the commit just before it.
  2. Run git rebase -i abc1234, where abc1234 is the commit just before the unwanted one.
  3. In the editor, replace pick with drop on the target line.
  4. Save and close the editor.
  5. Resolve any conflicts, run git add on the fixed files, then git rebase --continue.
  6. Check the result with git log --oneline and run your tests.

Interactive mode lists each commit after the one you name, merge commits excluded, and replays whatever stays in the list. That replay is the core mechanic of git rebase.

▶ GIT EXPLAINED

If you commit, branch, or merge code every day, it helps to know how Git came to be. This video covers how Linus Torvalds created Git in just 10 days after a license dispute and explains how its data model actually works.

Linus Torvalds Built Git in Days Out of Spite
Built in 10 Days Out of Spite. Git and its data model explained in plain language.

Both drop and deleting the line remove the commit. The difference shows up when rebase.missingCommitsCheck is set to warn or error. A deleted line then triggers a warning or stops the rebase, while drop passes silently.

When later commits touched the same lines as the dropped one, the rebase halts with conflict markers. Work through them as in this guide to resolving merge conflicts in Git, then continue, skip that patch with --skip, or return to the original branch with --abort.

Merge commits get dropped from the todo list by a plain rebase, which flattens the branch. Adding --rebase-merges keeps the branch structure, though conflict fixes inside those merges have to be redone by hand, according to the rebase manual.

There’s also a slower route. Create a clean branch from the parent and run git cherry-pick on each commit you want to keep. A mistake costs nothing because the original branch stays untouched.

How to Push the Rewritten History to GitHub

Push with git push --force-with-lease origin main, replacing main with your branch name. GitHub accepts the update only if the remote branch still sits where your remote-tracking branch says it does.

A plain git push fails after a reset or rebase because the remote tip is no longer an ancestor of your branch, and Git rejects any update that is not a fast-forward. git status reports the two histories as diverged.

Every commit after the deleted one also gets a new commit hash, since a hash covers the commit’s parent. Old and new history share nothing past the rewrite point.

A branch protection rule rejects the push even with the right flags. The next section explains that error.

The lease version rejects the push when the remote branch moved after your last fetch, and it accepts an explicit expected commit, as in --force-with-lease=main:abc1234. It has one weak spot. An editor or cron job running git fetch in the background updates your remote-tracking branch and defeats the check.

Plain git push --force succeeds whenever the remote accepts forced updates. It skips every lease check, so it can wipe out commits teammates pushed in the meantime.

The Git push manual (last revised in version 2.55, dated 2026-06-29) reserves plain --force for cases where losing history is the intent. That rules it out on any shared branch.

Adding --force-if-includes closes the background fetch gap. It checks that the remote tip appears in your local reflog before the push goes through.

What collaborators must do

Teammates must replace their local copy of the branch with the rewritten one. A plain git pull merges their old history with the new, which brings the deleted commit straight back.

They should run git fetch first, then reset to the remote branch.

  1. git fetch origin
  2. git switch main
  3. git reset --hard origin/main

Teammates holding unpushed commits on top of the old history should run git rebase --onto origin/main origin/main@{1} right after the fetch. A bare git rebase origin/main can resurrect the deleted commit, the rebase manual warns.

Why Does GitHub Reject the Force Push on a Protected Branch?

A branch protection rule blocks force pushes by default, and the same rule blocks deleting the branch. GitHub’s protected branches page states both defaults.

Protected-branch rejections show up in this format: remote: error: GH006: Protected branch update failed for refs/heads/main. No flag on git push overrides the rule, so the fix lives in repository settings.

The options differ in how much risk they carry:

  • Enable force pushes on the rule, either for everyone with write access or only for named people and teams.
  • Check the bypass setting. The restrictions skip admins and roles with bypass permission unless “Do not allow bypassing the above settings” is on.
  • Skip the rewrite and merge a revert through a pull request.

Rulesets are the alternative to branch protection rules. Several rulesets can apply to one branch at once, while only a single protection rule can, so check both places when the rejection persists.

Enabling force pushes does not override other rules. A linear history requirement still rejects merge commits, even from an allowed user.

GitHub’s own documentation warns that a force push can remove commits other people built on, or point a branch at commits no pull request approved. So allow only yourself, push, then switch the setting off again.

How to Undo a Pushed Commit Without Rewriting History

Run git revert abc1234 to record a new commit that reverses the old one, then push with a plain git push. The original stays in the history, so nobody has to repair a local copy.

  1. Run git log --oneline and copy the hash of the commit to undo.
  2. Make sure the working tree is clean, because revert refuses to start otherwise.
  3. Run git revert abc1234.
  4. Write a message that says why the change is being reversed.
  5. If conflicts appear, fix the files, run git add, then git revert --continue.
  6. Run git push origin main.

Git revert works on any commit, however old, because it applies the inverse of that commit’s diff on top of the current tip. The revert is a commit too, so it can be reverted later if the original change turns out to be needed.

Merge commits need -m 1 so Git knows which parent counts as the mainline. Without it, Git cannot revert a merge, since it cannot tell which side to keep (Git documentation).

Reverting a merge declares the merged changes unwanted. A later merge of the same branch only brings in commits that were not ancestors of the reverted merge. The manual says that may or may not be what you want. If the branch has to merge again, revert the revert first.

Revert fixes behavior. It does nothing for secrets, because the original commit stays readable in history.

Can You Recover a Deleted Commit?

Yes, as long as the reflog entry still exists and garbage collection has not removed the object. The reflog records where HEAD pointed before each reset or rebase, so the deleted hash is one command away.

  • git reflog lists earlier positions of HEAD, such as HEAD@{2}
  • git branch rescue abc1234 pins the hash from the reflog to a new branch
  • If you only need that single commit back, git cherry-pick abc1234 reapplies it on the current branch

The reflog lives in your local clone only. A fresh clone has no record of your earlier resets, but a teammate’s clone from before the rewrite still holds the commit.

Default values from the git-gc manual (last revised in version 2.52, 2025) set the clock. gc.reflogExpire keeps reflog entries in general for 90 days, while gc.reflogExpireUnreachable cuts that to 30 days for entries no longer reachable from the current tip. gc.pruneExpire adds a grace period of 2 weeks before unreachable loose objects are pruned, and gc.auto triggers automatic packing at roughly 6700 loose objects.

The 90-day figure usually cited for reflog recovery is only half the picture. A commit removed by a reset, amend or rebase is unreachable from the tip, so the shorter 30-day default applies to it.

Expiry only runs when gc runs, either by hand or automatically once loose objects pass the threshold, so a quiet repository may keep the commit longer. After that, git prune removes the unreachable object once the two-week grace period has passed.

Treat 30 days as the working window, and rescue the commit into a branch the same day.

When Deleting a Commit Does Not Remove the Data

Rewriting a branch and force pushing does not erase the commit everywhere. It can stay reachable in clones and forks, by its SHA in cached views on GitHub, and through pull requests that reference it (GitHub Docs, 2026).

If the data is a password, token or key, revoke or rotate it first. GitHub’s guidance notes that this alone may settle the problem, which makes the history rewrite optional.

  1. Revoke or rotate the exposed secret.
  2. Install git-filter-repo 2.47 or later, the minimum version that supports the --sensitive-data-removal flag, and make a fresh clone.
  3. Run git-filter-repo --sensitive-data-removal --invert-paths --path src/config/secrets.env, or use --replace-text ../passwords.txt to scrub text instead of a file.
  4. Count the affected pull requests with grep -c '^refs/pull/.*/head$' .git/filter-repo/changed-refs. The rewrite becomes irreversible once you push.
  5. Run git push --force --mirror origin. Pushes to refs/pull/ refs fail because GitHub marks them read-only, and any other failure points to branch protection that needs switching off temporarily.
  6. Contact GitHub Support with the repository name, the pull request count and the First Changed Commit(s) from the filter-repo output.
  7. Tell collaborators to re-clone, or to rebase their branches instead of merging them.

BFG Repo-Cleaner is the other common tool. It’s a Java program (Java 11 or above) that strips passwords and oversized blobs from a mirror clone. By default it leaves the latest commit on the HEAD branch untouched, so fix the current files and commit before running it, according to the project’s documentation.

BFG updates history without physically deleting the data. A local git reflog expire --expire=now --all followed by git gc --prune=now --aggressive finishes the job.

A commit that exists in a fork stays accessible there, and GitHub cannot hand over the fork owners’ contact details. You have to ask those owners to clean up or delete the fork.

Side effectCause
RecontaminationA teammate with an old clone runs git pull, then git push
New hashesEvery commit after the leak is rewritten
Lost signaturesgit-filter-repo strips commit and tag signatures
Broken diffsClosed pull requests lose their internal references

The sources split the cleanup between two places. The BFG project has you expire the reflog and run gc in your own clone, while GitHub Docs add a step no clone can perform. Support runs garbage collection on the server and removes cached views.

Git’s push manual explains why that matters. A forced update leaves the old commits dangling on the remote until a gc there prunes them.

Common Questions About Deleting a Commit in GitHub

Can I delete a commit in GitHub Desktop?

Not as a true delete. Right-click a commit in the History tab and choose Revert Changes in Commit, which adds a new commit and keeps the original in history (GitHub Docs, 2026).

For the latest unpushed commit, the Undo option returns its changes to your working files.

How do I remove a commit from an open pull request?

Remove it from the pull request’s branch, then force push that branch. The pull request picks up the new history.

Use git reset for the tip, or interactive rebase with drop for a commit further back, and push with --force-with-lease.

How do I delete a commit from a fork?

Rewrite the fork’s branch locally and force push it to the fork. The upstream repository is untouched.

If the commit also reached upstream through a merged pull request, it needs a revert pull request there. GitHub Docs (2026) adds that the commit stays accessible in any other fork that contains it.

How do I delete all commit history in a repository?

Run git checkout --orphan clean-history, commit all current files, rename that branch to main, then git push --force origin main. Branch protection has to allow the force push.

Old commits can still resolve by SHA from clones, forks and cached views. A leaked secret needs git-filter-repo and a rotated credential instead.

Can I change a commit message instead of deleting the commit?

Yes. git commit --amend rewrites the latest message, and interactive rebase with reword handles older commits.

Either way the commit gets a new commit ID, so a pushed commit needs a force push (GitHub Docs, 2026).

Can I delete several commits at once?

Yes. git reset --hard HEAD~3 removes the last three commits, and git rebase -i with drop on each unwanted line removes any set from a branch.

On a shared branch, git revert -n over a commit range undoes them without rewriting history.

Keeping the Next Bad Commit Off the Remote

Deleting a commit in GitHub costs a history rewrite and a round of teammate cleanup, so the cheaper fix is stopping the bad commit before the push.

A few safeguards do that, and they act at different moments:

  • A pre-commit hook that scans for secrets
  • A pull request requirement on the main branch
  • Blocked force pushes on shared branches, so history can’t be rewritten by accident

The hook acts before the commit exists, so a caught mistake costs nothing. Anything that slips past it and lands on a feature branch still has to survive review before reaching main.

There’s a catch. With force pushes blocked, every future removal on that branch becomes a revert unless an admin lifts the rule temporarily. That cost is worth paying, since a revert never strands a teammate’s clone.

This ordering reflects GitHub Docs and the Git 2.56 manuals as checked on 4 October 2026. For the credential side of the problem, see this guide to hiding an API key in GitHub.

Bogdan Sandu

Stay sharp. Ship better code.

Every week: one curated article, one tool worth knowing, one tip you can use tomorrow. No noise, no padding.