{"id":5344,"date":"2026-08-30T16:00:36","date_gmt":"2026-08-30T07:00:36","guid":{"rendered":"https:\/\/donguri3.net\/server-tech\/git-remove-file-history-filter-repo-2\/"},"modified":"2026-08-30T16:00:37","modified_gmt":"2026-08-30T07:00:37","slug":"git-remove-file-history-filter-repo","status":"publish","type":"post","link":"https:\/\/donguri3.net\/en\/server-tech\/development-programming-git\/git-remove-file-history-filter-repo\/","title":{"rendered":"[Git] How to Completely Erase Accidentally Committed Files from History (git-filter-repo)"},"content":{"rendered":"<p>Today, we&#8217;re diving into a technical guide on doing a &#8220;deep clean&#8221; of a Git repository.<\/p>\n<p>When you&#8217;re deep in the zone of development, it&#8217;s surprisingly easy to accidentally commit something you shouldn&#8217;t have and push it to the remote repository.<\/p>\n<ul>\n<li>Confidential information: secret.txt, API keys, .env files.<\/li>\n<li>Bloated repository size: huge test videos, zipped archives, node_modules.<\/li>\n<\/ul>\n<p>If you panic, run git rm to delete the file, and commit again, all you&#8217;ve really done is added a new history entry saying &#8220;this was deleted.&#8221; The data for that file still stubbornly remains in your past commit logs (inside the .git directory).<br \/>\nThe repository size won&#8217;t decrease, and anyone going back through the history will still be able to see the contents.<\/p>\n<p>In this post, I&#8217;ll walk through the surgical procedure to make specific files &#8220;as if they never existed from the very beginning of history&#8221; using git-filter-repo, the modern tool officially recommended by Git.<\/p>\n<h2>Goal: Rewriting History and Forcing Synchronization<\/h2>\n<p>Our goals for this operation are as follows:<\/p>\n<ol>\n<li>Complete Eradication: Physically eliminate the specified file (let&#8217;s use secret.txt as an example) from all past commits.<\/li>\n<li>Rewriting History: Recalculate commit hashes (IDs) accompanying the file deletion to reshape the history.<\/li>\n<div>3. Forced Overwrite: Overwrite the corrupted remote repository&#8217;s (GitHub, GitLab, etc.) main branch with the clean history.<\/div>\n<\/ol>\n<h2>Preparation: The Ultimate Vacuum Cleaner, git-filter-repo<\/h2>\n<p>While git filter-branch and BFG Repo-Cleaner used to be the go-to tools, git-filter-repo is now the official recommendation. Written in Python, it runs extremely fast and is designed to handle complex histories safely.<\/p>\n<h3>Installation (Requires a Python environment):<\/h3>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">pip install git-filter-repo<\/pre>\n<p>*If you are on a Mac using Homebrew, you can also install it via brew install git-filter-repo.<\/p>\n<h3>[WARNING] Backup Before You Begin<\/h3>\n<p>The operation we are about to perform is destructive.<br \/>\nJust in case you accidentally wipe out necessary files, make sure to copy the entire repository directory to create a backup first.<\/p>\n<h2>Practical Steps: Eradicating a Specific File<\/h2>\n<p>We will proceed with the steps to purge a secret.txt file that was mistakenly mixed into the repository.<br \/>\n*The procedure is completely identical for huge video files or binary data\u2014just substitute the file name.<\/p>\n<h3>Confirming Existence<\/h3>\n<p>First, let&#8217;s check how deeply embedded that file is within Git&#8217;s history.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">git log --all -- secret.txt\ncommit 476b4bcd36ee3e1b900db9edb98ea82e12d61f90 (HEAD -&amp;gt; master)\nAuthor: nando &amp;lt;xxx@gmail.com&amp;gt;\nDate: Wed Feb 11 18:51:51 2026 +0900\n\nDELETE secret.txt\n\ncommit c0676094dab6734d147f14273275340655d5f513\nAuthor: nando &amp;lt;xxx@gmail.com&amp;gt;\nDate: Wed Feb 11 18:51:40 2026 +0900\n\nADD secret.txt<\/pre>\n<p>If logs are displayed, Git remembers the file.<\/p>\n<h3>Erasing from History (Execution)<\/h3>\n<p>Run the following command. This is where we perform the surgery.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">git filter-repo --path secret.txt --invert-paths<\/pre>\n<h4>Option Breakdown:<\/h4>\n<ul>\n<li>&#8211;path secret.txt: Specifies the file you want to delete (if it&#8217;s an entire directory, specify the directory name).<\/li>\n<li>&#8211;invert-paths: This is crucial. It instructs Git to **&#8221;keep everything except (invert) the specified path&#8221;** = **&#8221;discard the specified path.&#8221;**<\/li>\n<\/ul>\n<h3>If an Error Occurs Here<\/h3>\n<p>If you encounter the error <code>Aborting: Refusing to destructively overwrite repo history...<\/code>, that is a safety mechanism.<br \/>\nTo prevent accidental operations, git-filter-repo may refuse to run unless you are in a &#8220;clean state right after cloning.&#8221;<br \/>\nIf this happens, append the &#8211;force option to force execution.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">git filter-repo --path secret.txt --invert-paths --force\ngit log\ncommit 60219177b930da26f899b0675a2b8a67fdbc3b3c (HEAD -&amp;gt; master)\nAuthor: nando &amp;lt;xxx@gmail.com&amp;gt;\nDate: Wed Feb 11 18:51:13 2026 +0900\ninitial commit\ngit log --all -- secret.txt\n&lt;If nothing outputs, you're good&gt;<\/pre>\n<p>After running, the process completes in an instant. Run git log again to check, and if nothing appears, your local repository has been purified.<\/p>\n<h2>The Tough Part: Pushing to a Protected Remote<\/h2>\n<p>This is where many people stumble.<br \/>\nYour local environment is now clean, but the remote (GitHub, etc.) still holds the &#8220;corrupted history.&#8221; Furthermore, if this file has already been merged into the main branch, things get a bit more complicated.<\/p>\n<p>Normally, the main branch is &#8220;Protected&#8221; to prevent accidental operations, meaning Force Pushes that rewrite history are configured to be rejected.<\/p>\n<p>If you try to run git push origin main &#8211;force as-is, it will be blocked by an error like this:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">! &#x5B;remote rejected] main -&amp;gt; main (protected branch hook declined)<\/pre>\n<p>To break through this, you need administrator privileges to temporarily disable the rule.<\/p>\n<h3>Lifting Protection Rules<\/h3>\n<p>Open your GitHub or GitLab repository settings page (Settings -&gt; Branches) and edit the protection rules for the main branch.<br \/>\nTemporarily disable the rules altogether or check the box for &#8220;Allow force pushes.&#8221;<\/p>\n<h3>Executing the Force Push<\/h3>\n<p>The exact moment you lift the protection, push without delay.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">git push origin main --force<\/pre>\n<p>*If the change affects all branches, include the &#8211;all flag as well.<\/p>\n<h3>Restoring Protection Rules (Most Important)<\/h3>\n<p>As soon as the push succeeds, immediately return to the settings page and restore the protection rules.<br \/>\nLeaving this undone leaves a lingering risk that someone might accidentally destroy the history again later.<\/p>\n<h3>Thorough Communication with Team Members<\/h3>\n<p>Rewriting history comes with major side effects.<br \/>\nBecause every single commit ID in the repository has changed, your history will no longer align with the repositories your teammates have on their local machines.<\/p>\n<p>If a team member continues working from their &#8220;locally corrupted history&#8221; without knowing about your fix and pushes their changes, secret.txt will rise like a zombie, tangling the history beyond easy recovery.<\/p>\n<p>Once you finish the operation, make sure to thoroughly communicate the following:<\/p>\n<blockquote>\n<p>[Important Notice]<br \/>\nWe have rewritten the history to remove unnecessary files (such as secret.txt) from the repository.<br \/>\nPlease do not push or pull from the repository you currently have on your local machine.<br \/>\nDelete your existing folder and perform a fresh git clone.<\/p>\n<\/blockquote>\n<p>This is the safest and only real solution.<\/p>\n<h2>Summary<\/h2>\n<ul>\n<li>Deletion tool: git-filter-repo is the only way to go.<\/li>\n<li>Error handling: If an error pops up, muscle through it with &#8211;force.<\/li>\n<li>Remote synchronization: Temporarily lift branch protection and run push &#8211;force.<\/li>\n<li>Team coordination: Have everyone re-clone the repository.<\/li>\n<\/ul>\n<p>They say &#8220;water spilled cannot be gathered,&#8221; but in the world of Git, you *can* gather it back\u2014at the cost of powerful tools, administrative rights, and some friction for your team.<br \/>\nHowever, that cost is by no means cheap.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Today, we&#8217;re diving into a technical guide on doing a &#8220;deep clean&#8221; of a Git repository. When [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":2671,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_locale":"en_US","_original_post":"https:\/\/donguri3.net\/?p=2670","footnotes":""},"categories":[1172],"tags":[125,1072,51,430,47,835,251],"class_list":["post-5344","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development-programming-git","tag-api","tag-cli","tag-git","tag-github","tag-python","tag-tips","tag-251","en-US"],"_links":{"self":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/5344","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/comments?post=5344"}],"version-history":[{"count":1,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/5344\/revisions"}],"predecessor-version":[{"id":5347,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/5344\/revisions\/5347"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/media\/2671"}],"wp:attachment":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/media?parent=5344"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/categories?post=5344"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/tags?post=5344"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}