You know the feeling. You use one laptop for everything — work projects, personal side-projects, open-source contributions. You've got your josh@megacorp.com work email and your cooldev42@gmail.com personal email, and Git doesn't care which one you meant to use. It just grabs whatever's in your global config and stamps it on every commit.
Then one day you look at your commit history and realize:
- Your personal email is plastered across 3 months of commits on your company's private repo
- Your work email is the author on half the commits in your personal open-source project
- That weekend hackathon project? Yeah, that's got your corporate identity all over it
Manually fixing this is a nightmare. You'd need to run git filter-repo or git filter-branch in every single repo, write mailmap files, snapshot and restore remotes (because filter-repo deletes them), hope you didn't mess anything up, and then force-push everything. For each repo. No thanks.
git-identity-rewriter does all of that for you. Point it at a directory, tell it the wrong identity and the right one, and it'll find every affected repo, back everything up, rewrite the history, restore your remotes, and optionally force-push — all in one interactive session.
- Scans a directory (recursively) for Git repos that have commits with the wrong name/email
- Lets you pick which repos to fix via an interactive checkbox UI
- Backs up each selected repo as a verified zip archive before touching anything
- Rewrites the Git history using
git filter-repo --mailmap - Restores remotes that
filter-repowould normally delete - Optionally force-pushes the corrected history to
origin
The whole thing is interactive — no config files, no flags to memorize. Just run it and follow the prompts.
You'll need:
- Python 3.10+
- Git on your PATH
- git-filter-repo:
pip install git-filter-repo
Then install the tool itself:
cd git-identity-rewriter
pip install .For development:
pip install -e .git-identity-rewriterThat's it. The tool walks you through everything interactively. Here's what to expect:
Quick sanity check: makes sure git and git-filter-repo are installed and reachable. If something's missing, it'll tell you exactly what to install.
You pick a target directory (defaults to wherever you are), enter the old identity you want to replace, and the tool recursively scans for Git repos with matching commits. You'll see a table showing every repo it found, how many commits match, and any warnings.
An interactive checkbox menu pops up with all the discovered repos. Pick the ones you want to fix, or hit "Select All eligible" to grab everything. Repos that can't be rewritten (shallow clones, bare repos, active merge/rebase) are shown but disabled so you know why they're skipped.
If any selected repo has uncommitted changes, you'll get a heads-up and a chance to back out.
Before anything gets rewritten, every selected repo is zipped up into a timestamped backup folder. Each zip is verified after creation. If a backup fails (unreadable files, disk issues), that repo is automatically skipped from the rewrite — no backup, no rewrite.
You'll see a summary panel: how many repos, how many commits, total backup size. One last Y/N prompt before the rewrite happens. This is your "are you really sure?" moment.
The actual history rewrite runs via git filter-repo --mailmap. For each repo, the tool creates a temporary mailmap file, runs the rewrite, counts remaining old-identity commits (should be zero), and restores all remotes. You get a per-repo results table when it's done.
After the rewrite, you're asked if you want to auto-push to origin. If yes, the tool refreshes tracking refs first (git fetch origin --prune), then uses --force-with-lease for branches and --force for tags. If a push fails for one repo, the others still go through.
Not ready to commit? (pun intended.) Enable dry-run when prompted and the tool will scan, let you select repos, and create backups — but skip the actual rewrite. Everything up to Phase 4 runs normally, so you can verify the backup process and see exactly what would be rewritten.
This tool rewrites Git history, which is inherently destructive. So safety is baked into every step:
- Backups are created before any rewrite happens. Each repo gets its own verified zip archive.
- Backup verification — every zip is tested after creation. If verification fails, that repo is skipped.
- No rewrite without a backup. Period.
- Ctrl+C at any point exits cleanly, and any backups already created are preserved.
- JSON audit log — a
rewrite-log.jsonis written to the backup folder with full details of what happened.
Backups live here:
<target-dir>/.git-rewrite-backups/<timestamp>/
├── repo-name-<path-hash>.zip
├── another-repo-<path-hash>.zip
└── rewrite-log.json
Zips include the full repo (minus node_modules, __pycache__, .venv, build, dist, and similar artifact directories). If the target directory is itself a Git repo, the backup folder is automatically added to .git/info/exclude so it won't clutter your git status.
If something goes sideways:
- Delete (or rename) the rewritten repo folder
- Unzip the corresponding backup archive
- Move it back to the original location
That's it — you're back to exactly where you started.
If you chose not to auto-push in Phase 6 (or if auto-push failed for a repo), you can always push manually after verifying the rewrite looks correct:
cd <repo-path>
git push --force-with-lease origin --all
git push --force-with-lease origin --tagsHeads up: force-pushing overwrites remote history. If other people work on the same repo, give them a heads-up first so they can rebase or re-clone. Don't be that person who silently force-pushes on a Friday afternoon.
The tool handles a bunch of tricky situations so you don't have to think about them:
| Situation | What happens |
|---|---|
| Uncommitted changes | You'll get a warning and a confirmation prompt — proceed at your own risk |
| Shallow clones | Blocked from selection — filter-repo can't rewrite partial history |
| Active merge or rebase | Blocked — finish your merge/rebase first |
.git/index.lock exists |
Warning shown — usually means another Git process is running |
| Bare repositories | Blocked — bare repos aren't supported |
| Submodules | Warning shown — submodule state might need manual attention |
| Git worktrees | Handled correctly — the tool resolves .git file references |
| Backup fails | That repo is skipped from rewrite — safety first |
| Rewrite fails | Logged with details, other repos continue normally |
| Ctrl+C | Clean exit, backups are preserved |
Private tool — not licensed for redistribution.