Assumed Audience: Hackers, Git users, code archeologists, and anyone who would need to do forensics on Yzena repositories.

Epistemic Status: Very sorry and repentant.

Forgive me, for I have sinned.

I had to create a new GPG key, separate from my personal one, and I had to re-sign every single commit in a few repos with the new key.

This is rebase on a whole new level.

These were my requirements:

  • The author date needs to be the same.
  • The committer date (yes, it’s different from the author date) needs to be the same.
  • It has to deal with a repo that is a bunch of combined repos.

It turns out that the first is easy, but the second is nigh impossible, and the third needs some manual work.

For obvious reasons, do NOT try this at home!

If you must do this, do it on a full new clone of the repo like I did. I had to try a couple of times to get it right, and I never had the possibility of losing data.

The resulting command:

$ git rebase --exec \
  'GIT_COMMITTER_DATE="$(git show --format=tformat:%aD XXXXXXXXX | head -n1)" \
  git commit --amend --author="Gavin D. Howard <email_redacted>" --no-edit
  --no-verify -S' --committer-date-is-author-date -i --root --rebase-merges

Here’s the explanation:

  • git rebase

    I said that this is rebase on a whole new level, but it is still a rebase.

  • --exec '...'

    git rebase “recommits” all of the target commits, changing things that the user asks to be changed, and it can execute a command on each recommit.

    • GIT_COMMITTER_DATE="$(...)"

      This sets GIT_COMMITTER_DATE, which is the only way I could find to set the committer date. What this does is puts the result of git show ... into the variable.

      • git show --format=tformat:%aD XXXXXXXXX ...

        This shows a commit with the short hash of XXXXXXXXX. This is a placeholder for later. The given format string just tells git to show the author date of the commit.

      • ... head -n1

        This takes the first line of the git show because the git show will show what you tell it to, as well as the patch. This filters out the patch.

    • git commit --amend ...

      This is what does the recommit. It does an --amend to change the commit I’m trying to rebase.

    • ... --author="Gavin D. Howard <email_redacted>" ...

      This sets the author to the new commit. I needed to set this for legal reasons.

    • ... --no-edit --no-verify -S

      These are generic options to ensure the commit isn’t wrongly edited or have useless hooks run. And also sign.

  • --committer-date-is-author-date

    This was an attempt to change the committer date to the author date. It didn’t work, but I just copied and pasted, so this ended up in the final command.

  • -i

    This makes the rebase interactive. This isn’t important because --exec will make the rebase interactive.

  • --root

    The option makes the rebase happen from the start commit, so I wouldn’t have to find the start commit.

  • --rebase-merges

    This is the option that made the rebase work in the presence of multiple histories. It tells the rebase to also rebase merges instead of just regular commits.

But even that isn’t everything.

When you run the command, git does nothing you expect. Instead, it pulls up your $EDITOR. For me, it’s Neovim.

In the editor is a file. There are two lines for every commit, one to allow the user to pick the commit or reject it, and one saying what command will be executed when rebasing that commit.

There are a few more lines, but you don’t need to worry about them unless there are multiple branches. Those lines basically select the branch.

The fact that each commit has its own “exec” line means that the user can set a different command for each commit. I used this to my advantage.

This was the reason for the XXXXXXXXX in each command. I used that to set a Vim macro to grab the hash for the macro from the first line for the commit and then replace the placeholder with the commit hash. This is what made the GIT_COMMITTER_DATE trick work.

And then, once you save the file and quit the editor, git starts running. This is the actual rebase process.

Because of the repo with multiple histories, there were a few merge conflicts. This is the manual part, but it turned out to not be a problem for me.

Anyway, that’s the full explanation of how I did it.

It wasn’t fun; I had one repo with over 4000 commits to re-sign. It was quite tedious to use the Vim macro because I wanted to make sure that the macro worked on every line.

In conclusion, don’t do this. Remember, it’s a sin.

But it was…enlightening.