Repository housekeeping: prune unreachable objects
Deleting a branch, tag or pull request in Git does not delete its commits. The commits become unreachable: no ref points at them any more, but the objects stay on the server and can still be fetched or viewed by anyone who knows their SHA, until Git’s own garbage collection removes them at some later point.
Prune unreachable objects removes them now. Use it when a commit should never have been pushed, for example because it contained a secret, a customer’s personal data or a file you are not allowed to store. Org owners run it themselves from the console, and every run ends with a written confirmation you can keep.
Before you start: rotate the secret
If the commit contained a secret (a password, API key, token or private key), rotate or revoke the secret first. Removing the commit does not make the secret invalid. Anyone who cloned or fetched the repository while the commit was there has a copy, and so do forks, mirrors and CI logs (see What it does not remove). Pruning limits who can obtain the commit from now on; it cannot recall copies that already exist.
Push protection blocks most secrets before they reach the server in the first place.
Who can run it
Org Owners. The page is in the org admin at:
frem.sh/<your-org>/_admin/repos-overview/<repo>/housekeeping
You need Owner access to the org admin, through your normal sign-in, which requires multi-factor authentication. There is no separate confirmation step beyond that. To start a run you type the repository name in full, so a run cannot be started on the wrong repository by mistake.
Step by step
- Rotate any leaked secret. See above.
- Delete what holds the commit. Delete every branch, tag or pull request that still contains the commit, or rewrite the branch history so the commit is no longer part of it and force-push. A commit that any ref still reaches is not unreachable, and pruning will not remove it.
- Preview. Open the housekeeping page and run the preview. You can paste the SHAs of the commits you want gone (up to 20) and the preview tells you, for each one, whether it will be removed or what still holds it. See Reading the preview.
- Run it. Choose whether to include orphaned LFS objects, type the repository name to confirm, and start the run. The page shows its progress.
- Download the confirmation. When the run has finished, download the written confirmation as PDF or JSON and keep it with your incident record. See The written confirmation.
Reading the preview
The preview makes no changes. It lists:
- For each SHA you pasted: whether it exists in the repository, and if a ref still holds it, which one (a branch, tag, pull request or another ref). Delete that ref and run the preview again. A SHA that only a reflog entry still holds is removed by the run.
- Unreachable commits that will be removed, with their commit message subject.
- Unreachable commits within the grace period: too recent to remove now. See The 30-minute grace period.
- Forks of the repository, by name. A fork is a separate copy and is not changed. See Forks.
- Other items the run will remove: the number of cached archive downloads, restore-branch entries and, if you include them, orphaned LFS objects.
- Why the run cannot start, if it cannot. For example: a run is already in progress for the repository, or the repository is above the self-serve size limit.
The 30-minute grace period
Objects created or refreshed in the last 30 minutes are always kept. This protects pushes that are in progress while the run happens: a push writes its objects before it updates the branch, and for that short moment the new objects look unreachable. The grace period is fixed and cannot be shortened.
If a commit you want removed is listed as within grace, wait until 30 minutes have passed since you deleted what held it, run the preview again, then start the run.
What it removes
- Unreachable Git objects: the commits, and the trees and file contents that only those commits use, together with the reflog entries that still held them.
- The commit from the commit-graph, so the commit no longer appears in history views, search or by its SHA.
- Cached archive downloads (zip and tar) of those commits.
- Cached copies of fetch packs for the repository, which the server keeps to answer repeated clones and fetches faster.
- Restore-branch entries for deleted branches that pointed at those commits, so the branch can no longer be restored from the repository’s list of deleted branches.
- Optionally, orphaned LFS objects: LFS files that no commit in the repository references any more and that were uploaded more than 24 hours ago. You choose this when you start the run.
When the run has finished, a URL that showed the commit returns not found, and a git fetch of the SHA fails.
What it does not remove
The run removes objects from this repository on fremforge’s servers. It does not reach copies held anywhere else, and it does not edit records that mention the commit:
- Forks. Each fork is a separate copy. See Forks.
- Existing clones and exports. Every clone, fetch and data export made while the commit was there still contains it. Ask the people concerned to delete their copy or clone again.
- Mirrors. A mirror of this repository, on fremforge or elsewhere, keeps its own copy.
- The repository’s wiki. The wiki is a separate Git repository and is not pruned.
- Actions logs and artifacts. A workflow that ran on the commit may have printed or stored its contents. Remove those separately.
- Webhook delivery payloads, which include commit SHAs, messages and author details.
- The activity feed, commit statuses and pull request timeline comments that mention the commit.
If one of these holds something you need removed and you cannot remove it yourself, contact support@frem.sh.
Forks
A fork is a full, separate copy of the repository, and the owner of the fork controls it. The preview lists the forks by name, so you know where else the commit may be. To remove the commit from a fork, the fork’s owner deletes the branch, tag or pull request that holds it there and runs the prune on the fork, or deletes the fork.
Backups
Pruning removes the objects from the live repository. It does not edit backups: fremforge’s backups are not changed to remove an individual customer’s data, and they are used only to recover the platform, never to serve the data to anyone. A pruned object therefore stays in the encrypted backups until those backups expire through their normal retention:
- Repository content (the Git objects): up to 30 days.
- Object-store copies (cached archive downloads and LFS objects): up to 13 months, in disaster-recovery storage.
Database records, such as restore-branch entries, follow the database backups. The Data Processing Agreement, §9 and Annex A.9, is the binding statement of each backup tier and how long it is kept; where this page and the DPA in force differ, the DPA applies.
The written confirmation
Every finished run produces a confirmation you can download as PDF or JSON. It records the repository, who ran it and when, the number of objects before and after the run, the number of archive downloads, restore-branch entries and LFS objects removed, and, for each SHA you pasted, whether it is now absent from the repository or what still holds it.
Keep it with your incident record. It is your evidence of when the data was removed from the live service; the Backups section above states how long copies remain after that.
Limits
- One run per repository per hour, and 10 runs per org per day. A request that is refused does not count.
- Self-serve size limit: 5 GiB. For a repository larger than that, contact support@frem.sh.
- Up to 20 SHAs can be pasted into one preview.
API
The preview and the results of a run can be read through the API:
GET /_app/api/v1/orgs/{slug}/repos/{repo}/housekeeping/prune/preview?sha=<sha>[,<sha>...]The status of a run and its written confirmation have their own GET endpoints, listed in the OpenAPI spec.
Starting a run is only possible from the console. The API cannot start one.