Rewrite and Resign Git Commit-History with Git-Filter-Repo

Rewrite and Resign Git Commit-History with Git-Filter-Repo

I have an existing private git-repository (with two branches), hosted on GitHub. I plan to make it public, but I don't want to make public the author email-address. So, the plan is to rewrite the commit-history, by replacing the <old-author-email> with the <new-author-email>, and resign the commits. Also, I want to maintain the time-stamps (this is important for me!).

So, based on extensive research I have come-up with the following steps:

  1. Clone the repository on my local machine.
  2. Create a python-3.x virtual-environment, and run pip install git-filter-repo. (source)
  3. Create a .mailmap file in the repository WORKING_DIR, with the email remapping information. (source)
  4. Run git-filter-repo --force --mailmap .mailmap. (source1, source2)
  5. Somehow resign the new-commits !!
  6. Then run git push origin <your_branch_name> --force, to rewrite the remote commit-history. (source)
    • If this step doesn't work, I would simply delete the original Github repository; Create a new one with the same name, and then push the local-repository to it.

The above steps (until step-4), rewrite the author-email and recompute the hashes, but discard the signatures. The documentation says: (source)

Since git filter-repo calls fast-export and fast-import to do a lot of the heavy lifting, it inherits limitations from those systems ... commits get rewritten meaning they will have new hashes; therefore, signatures on commits and tags cannot continue to work and instead are just removed (thus signed tags become annotated tags)

So, to resign the commits, I found the following method (source).

git filter-branch --commit-filter 'git commit-tree -S "$@";' HEAD

But using git filter-branch is not recommended (source), even by Git's own documentation (source).

Questions:

  • And, is there a way to rewrite author-email and resign the new-commits, all using git-filter-repo?
  • And if no! then, how to rewrite the author-email over the entire commit-history and resign the new-commits? (a better way than mine!)

1 Answer

This method DOESN'T work if the repository has multiple branches. So, before getting started, merge all secondary branches in to the primary-branch (usually master/main), as per your requirement. And then delete all the secondary-branches. (both in the local and the remote repositories)

I came up with a possible solution:

  1. Clone the repository on my local machine.
  2. Create a python-3.x virtual-environment, activate it, and then run pip install git-filter-repo GitPython.
  3. cd <repo-directory>/../, i.e., the immediate parent-directory of the local-repository's WORKING_DIR
  4. Run the following python-script: This will create a .mailmap file, right outside the <repo-directory>, with all the unique author/committer-name and email combinations appearing in the said repository's commit history - to be replaced with the NEW_NAME and NEW_EMAIL. (Further edit the code to exclude certain names/emails from being rewritten)
from git import Repo

repo_path = '<repo-directory>'
repo = Repo(repo_path)

commits_list = list(repo.iter_commits())
unq = set()
for cmt in commits_list:
    unq.add((cmt.committer.name, cmt.committer.email))
    unq.add((cmt.author.name, cmt.author.email))

NEW_NAME = "<new-author-name>"
NEW_EMAIL = "<new-author-email>"

lines = list()
for c in unq:
    lines.append(f"{NEW_NAME} <{NEW_EMAIL}> {c[0]} <{c[1]}>\n")

with open(".mailmap", "w") as f:
    f.writelines(lines)
  1. cd <repo-directory>, and then run git-filter-repo --mailmap ../.mailmap. --> Running this command, for some reason, seems to remove the remote repository links, i.e. git remote -v returns nothing.
  2. To reset the repository's remote, run git remote add origin <remote-repo-url>
  3. DON'T DO THIS: if you have multiple branches To sign the commits, run git filter-branch --commit-filter 'git commit-tree -S "$@";' HEAD. Also, you might have to add an -f flag to make it work. for the branch separately, i.e. you will need to checkout into each branch and run the said command.
    • After performing the above steps, I ran git fsck --full --strict and got no errors. So, I think it worked fine!
    • But is there a better way to check for the repository's integrity?
  4. Finally, run git push origin <primary-branch> --force to over-write all the remote branches with the updated commit history.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Sarah Jenkins
Author

Sarah Jenkins

Sarah Jenkins is a veteran tech journalist with over 12 years of experience covering artificial intelligence, mobile innovations, and digital ethics. Her insights have appeared in leading technology publications worldwide.