Scan GitHub orgs and user profiles for a committed email address and rewrite it out of history.
Project description
git-scrub
CLI tool to scan GitHub organizations and user profiles for a committed email address, then rewrite it out of history across every affected repo.
Why this exists
git filter-repo handles the local rewriting step well, but provides no tooling to:
- Discover which repos across a GitHub org or user profile contain a given email
- Clone, rewrite, and force-push each one automatically
- Verify the result via the GitHub API after each push
- Report a clean summary across dozens or hundreds of repos
git-scrub automates the entire workflow. Point it at an org or a username, give it the old and new email, and it handles the rest.
Common use case: you've left a job or graduated, and you want to scrub your old institutional email (you@university.edu) from every repo you've ever touched — replacing it with your personal GitHub no-reply address.
How it works
1. Discover → list every repo in the target org(s) / user profile(s)
2. Scan → check each repo's commit history for the old email
(both author and committer fields)
3. Report → show a table of dirty repos and commit counts
4. Rewrite → for each dirty repo: clone → git filter-repo → force-push
5. Verify → confirm via the GitHub API that the email is gone
6. Clean up → temp clone is deleted after each repo
Steps 4–6 only run with --fix. Everything up to step 3 is always safe.
Prerequisites
- Python 3.9+
git-filter-repobrew install git-filter-repo # or pip install git-filter-repo
- GitHub CLI (
gh) authenticated or aGITHUB_TOKENenv var withreposcope
Installation
pipx install git-scrub # recommended
# or
pip install git-scrub
Usage
Scan (dry-run, always safe)
# Scan a whole org
git-scrub --old-email old@work.edu --org my-org
# Scan all repos under a GitHub username (public + private)
git-scrub --old-email old@work.edu --user myusername
# Multiple targets at once
git-scrub --old-email old@work.edu --org org1 --org org2 --user myusername
# Target a single specific repo
git-scrub --old-email old@work.edu --repo owner/repo-name
# Scan a non-default branch
git-scrub --old-email old@work.edu --org my-org --branch develop
Rewrite and push (--fix)
git-scrub \
--old-email old@work.edu \
--new-email 123456+myusername@users.noreply.github.com \
--org my-org \
--fix
# Skip per-repo confirmation prompts
git-scrub ... --fix --yes
JSON output (for scripting / CI)
git-scrub --old-email old@work.edu --org my-org --json
{
"scanned": 42,
"dirty": [
{ "repo": "my-org/some-repo", "commits": 7 }
]
}
Token
# Via env var (useful in CI)
GITHUB_TOKEN=ghp_... git-scrub ...
# Via flag
git-scrub --token ghp_... ...
If neither is provided, git-scrub calls gh auth token automatically.
Options
| Flag | Description |
|---|---|
--old-email |
Email to find (required) |
--new-email |
Replacement email (required with --fix) |
--org |
GitHub org to scan. Repeatable. |
--user |
GitHub username to scan all their repos. Repeatable. |
--repo |
Specific owner/repo to target. Repeatable. |
--branch |
Branch to scan (default: each repo's default branch) |
--fix |
Rewrite history and force-push |
--dry-run |
Explicit scan-only mode (already the default) |
--yes / -y |
Skip per-repo confirmation prompts |
--json |
Output results as JSON |
--token |
GitHub token (or set GITHUB_TOKEN) |
-V / --version |
Print version and exit |
Notes
- Dry-run is the default. Nothing is written without
--fix. - Both
authorandcommitteremail fields are checked and rewritten. - The scan checks each repo's default branch (or
--branch).git filter-reporewrites all branches and tags when fixing, so the full history is cleaned regardless. - Force-pushing rewrites history for all collaborators on a repo — coordinate with your team before running on shared repos.
- GitHub's commit attribution UI may take a short time to update after a push.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file git_scrub-0.1.1.tar.gz.
File metadata
- Download URL: git_scrub-0.1.1.tar.gz
- Upload date:
- Size: 9.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
92645bd10a5ff99338689bb6fcbc0da0b4d3555ce80e5dbc51ca5274deabf99b
|
|
| MD5 |
098d12eff2d5679fe8fd5c5ec7b654d5
|
|
| BLAKE2b-256 |
791944ccafedc582ef6777ca14373a7a7b11b3e86c59ab57c9248f836b263b55
|
Provenance
The following attestation bundles were made for git_scrub-0.1.1.tar.gz:
Publisher:
publish.yml on tabato/git-scrub
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
git_scrub-0.1.1.tar.gz -
Subject digest:
92645bd10a5ff99338689bb6fcbc0da0b4d3555ce80e5dbc51ca5274deabf99b - Sigstore transparency entry: 1395750452
- Sigstore integration time:
-
Permalink:
tabato/git-scrub@cac3bff1249ee16669bf83653d6770c088ccb26d -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/tabato
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cac3bff1249ee16669bf83653d6770c088ccb26d -
Trigger Event:
push
-
Statement type:
File details
Details for the file git_scrub-0.1.1-py3-none-any.whl.
File metadata
- Download URL: git_scrub-0.1.1-py3-none-any.whl
- Upload date:
- Size: 10.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e24a408f14e9e03a794394bacbf4a355671381a97dedbdd63d3babec003bec31
|
|
| MD5 |
87e9b0a4813a924971ac96d94665ad67
|
|
| BLAKE2b-256 |
0d965f5dbd99dc6eeaec5158802f881734cd07cf098de60fe8349de0165ae920
|
Provenance
The following attestation bundles were made for git_scrub-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on tabato/git-scrub
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
git_scrub-0.1.1-py3-none-any.whl -
Subject digest:
e24a408f14e9e03a794394bacbf4a355671381a97dedbdd63d3babec003bec31 - Sigstore transparency entry: 1395750491
- Sigstore integration time:
-
Permalink:
tabato/git-scrub@cac3bff1249ee16669bf83653d6770c088ccb26d -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/tabato
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cac3bff1249ee16669bf83653d6770c088ccb26d -
Trigger Event:
push
-
Statement type: