Docket

OWNING YOUR DATA

Git history is permanent

Deleting a file is not the same as deleting the data.

6 MIN READ Last updated 23 August 2026

Deleting a file from a git repository does not delete the data. git rm removes it from the current commit; every version before that stays in history, in every clone, and in every fork. If customer feedback lives in a repository, that matters the moment someone pastes an email address or a stray secret into a bug report.

This page is one part of a larger guide. For the whole subject in one place, see self-hosting a feature request board.

Why doesn't git rm actually delete anything?

Because git does not store files, it stores a chain of immutable snapshots, and git rm only changes what the newest one points to. Every earlier commit still points at the old version, sitting in the object store, reachable by anyone who looks.

git log --all --full-history -- posts/report-42.md
git show a1b2c3d:posts/report-42.md

The first command finds every commit across every branch that ever touched the file, including the ones after deletion. The second prints its exact contents at a specific commit. Neither needs more than a normal clone, and both work exactly as well after the file has been removed as before. This is not a bug, it is what makes git useful: the same property that lets you recover an accidentally deleted file six months later keeps a mistakenly committed email address recoverable for exactly as long.

What does git filter-repo actually do?

git filter-repo rewrites history rather than adding a commit on top of it. Point it at a path, and it walks every commit, strips the matching content from each one, and rebuilds history without it.

git filter-repo --path secrets.env --invert-paths

--path selects the file to act on, and --invert-paths flips the selection so that instead of keeping only that file, everything except it survives. Run it against a fresh clone made for the purpose, since filter-repo refuses to run on a repository it thinks might have other uncommitted work sitting in it.

The reason this works, and the reason it is disruptive, is the same fact: a commit's identity is a hash computed from its content and its parent's hash. Change a commit two hundred deep and it gets a new hash, and every commit built on top of it gets a new hash too. The rewrite does not patch history in place, it replaces it with a parallel history sharing nothing with the old one from that point forward.

Why does everyone have to re-clone afterward?

Because after a rewrite, nobody's existing clone shares any commits with the new history past the point of change. A normal git pull assumes shared ancestry and fast-forwards; there is none here, so force-pushing rewritten history and asking collaborators to pull invites a conflict pull cannot resolve. The practical answer is that everyone discards their clone and clones again, then reapplies by hand any unpushed local commits. That is the real cost of a rewrite: not the command, but every collaborator's afternoon.

Your own support centre, in your own repository

One payment, no subscription, unlimited products.

git filter-repo is the tool worth learning, not git filter-branch. Filter-branch is git's older built-in tool for the same job, and its own manual page now warns people to use filter-repo instead, because filter-branch operates one commit at a time through a slower, more error-prone process, and is easy to run in a way that leaves old objects reachable through a branch, tag, or reflog it never touched. Filter-repo was built to replace it, and is what the git project itself recommends.

What happens to copies GitHub or a fork already has?

Rewriting history and force-pushing it does not reach every place a copy of the old history exists. GitHub keeps its own cached views of a repository, separate from the ref data your force-push replaces, and those views, along with any pull request that referenced the old commits, can keep showing the removed content for a period afterward. GitHub's own guidance on removing sensitive data is to contact GitHub Support and ask them to purge the cached views and pull request references directly, since a force-push alone does not reach them.

Forks are a separate problem. A fork made before the rewrite is a full, independent copy as it stood at that moment, on infrastructure you do not control, and your rewrite does nothing to it. The same is true of any clone made before you acted. There is no technical mechanism that reaches into someone else's copy and removes data from it.

What does this mean for a request to delete personal data?

It means the technical reality is more complicated than "we deleted the file". Removing a commit from the default branch, or fully rewriting history with git filter-repo, does not guarantee the data is gone everywhere: other clones, forks, and GitHub's cached views can still hold it. Whether that satisfies a legal erasure obligation is a question for the reader and their own legal advisers, not something a technical guide can answer; what this guide can do is describe the mechanism accurately, so whoever answers the legal question is not working from the assumption that git rm behaves like deleting a row from a database. It sits alongside the other side of owning your own data, covered in the EU Data Act and SaaS export rights: getting data out is only half the picture, making it disappear again is the other half.

The practical answer: keep it out in the first place

Because removing personal data after the fact is unreliable, disruptive, and still cannot reach a fork made before you acted, the workable policy is not to let it into the repository as a committed file to begin with. That one decision does more than any rewrite ever will, because it carries no cleanup cost and leaves no gap where a fork or a cache keeps a copy you cannot touch.

This is worth being honest about for a git-backed board specifically, because it is a genuine trade-off of the model, not a hypothetical one. Docket's paid tiers store every post and comment as markdown files committed to your own repository, and encrypt the submitter's own email address at rest for exactly this reason. But the free-text body of a submission is not scrubbed automatically: paste an email address, a phone number, or a stray API key into it, and that becomes a permanent, git-tracked part of the repository the moment the commit lands, like any other line of any other file in it. Treating the submission body as a public, permanent record is the operator's job. Git will not do it for you, and rewriting history later does not do it reliably either.

Frequently asked questions

If I delete a file with git rm and commit it, is the data gone?

No. It is gone from the current version, which is what most people mean by "deleted", but it remains in every earlier commit that included it, retrievable by anyone with access to the repository's history using a normal git show or git log.

What is the difference between git filter-repo and git filter-branch?

Both rewrite history to remove content from every commit, but filter-branch is older, much slower since it works one commit at a time, and easy to misuse in a way that leaves old data reachable through a ref it never touched. Filter-repo was built to replace it, and git's own documentation now recommends it.

Does rewriting history remove the data from GitHub entirely?

Not immediately. GitHub caches its own views of a repository's history separately from the ref data a force-push replaces, and those views, along with pull requests that referenced the old commits, can keep showing the old content until GitHub Support is asked to remove them.

If someone forked the repository before I rewrote its history, do they still have the old data?

Yes. A fork is an independent copy taken at the moment it was made, on infrastructure you do not control, and rewriting your own history afterward has no effect on it.

Does rewriting history satisfy a data deletion request under something like GDPR?

That is a legal question for you and your own advisers, not a technical one. What can be said with certainty is the technical reality: rewriting removes data from the repository you rewrite, but other clones, forks, and cached copies elsewhere may still hold it, and that gap is worth knowing before anyone makes a legal representation based on it.