Monorepo Tooling Update: Signed Commit Support in Git 2.52

Burnout has kept me away from several different projects for years, as my file timestamps frequently remind me. It’s a good reminder that bit rot is a real thing – my code didn’t change, but the tools and the runtime environment did. This has been important from both a “breaking” and an “enabling” perspective.

One of the efforts I had intended to work on right before debilitating burnout hit me in full force was shepherding patches through git’s review process to support signed commits in fast-export and fast-import. In my original article on signed commits in monorepos, I described the problem of preserving commit signatures when splitting a monorepo back into standalone repositories. My temporary solution involved:

  1. Forking git to apply patches for --signed-commits support in fast-export and fast-import
  2. Forking git-filter-repo to add support for stashing signatures in commit messages during merge and restoring them during split

Fortunately, in the intervening years, someone else had the capacity to get the patches submitted – thank you so much, Christian Couder.

Quote
We have made some significant progress:
  • In Git 2.50 we added a --signed-commits=<mode> option to git-fast-export(1) for exporting commit signatures, and support in git-fast-import(1) for importing them.
  • In Git 2.51 we improved the format used for exporting and importing commit signatures, and we made it possible for git-fast-import(1) to import both a signature made on the SHA-1 object ID of the commit and one made on its SHA-256 object ID.
  • In Git 2.52 we added the --signed-commits=<mode> and --signed-tags=<mode> options to git-fast-import(1), so the user has control over how to handle signed data at import time.

Returning to this work, I planned to update git, verify the new support, and hopefully eliminate our fork.

And, as is the best practice when refactoring, the first step is to make sure things work in the baseline configuration. Mostly this is just a habitual check. I mean, really, nothing has changed in two years, it used to work then, it should work now, right?

Breaking: Where did all those remotes go?

My basic check was to run the split-and-update operation for a repository that I know was good and would have no changes. The way this works, briefly: each standalone repository is configured as a remote in the .gitconfig file. We rewrite monorepo history with git-filter-repo, extracting the history for a particular subdirectory, then push the result to the appropriate remote. The operation reports whether there are no changes, new changes to push, or incompatible history changes (signature problems, case-sensitive rename operations, etc.).

So, I expected that I’d split a repository out, and I would see that there were no updates, or maybe something broken in the history rewriting at worst. What I didn’t expect, and spent too many hours debugging, was a novel problem.

Pushing changes made on 'main' and all tags into 'libc/master'
fatal: 'libc' does not appear to be a git repository
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
Invalid remote name "libc": invalid remote name: "/Users/phillip/src/ea/monorepo/libc"

Now, it is well-documented that git-filter-repo removes the origin remote unless you set certain flags. But the other remotes we needed were still preserved and accessible. At least… that used to be the case. Somewhere along the way I seem to have updated our git-filter-repo fork, the behavior changed to remove all remotes (with there only being an option to preserve origin via flags), and I didn’t test the update to see the new behavior. Whoops.

Once I figured all of that out, I wasted time trying to prevent the loss of remotes before giving up and taking the expedient route. I updated our tooling scripts to:

  • Capture the remote for the associated project when starting the script
  • Restore the remote after finishing the filter operation
  • Push the changes

Thankfully, that got me back to the baseline working state.

Enabling: Signed Commit Support

The next step was to check that mainline git actually worked with our tooling. This meant updating our fork of git-filter-repo to use the mainline version instead of our hard-coded path to the fork (hey, nobody’s perfect). This mostly worked as-is, given that the baseline for our fork was the same as the patches that were submitted.

That left the git-filter-repo fork. I had previously implemented new options to support saving and restoring signed commits as part of rewriting operations. Some of the work I had done around signatures could certainly be cleaned up and submitted. But the real support I needed? I expect that would not be accepted.

You see, the monorepo was built with a specific strategy for maintaining the valid-in-standalone-repos-but-invalid-in-monorepo commit signatures. Signatures are stashed in the commit message during the merge into the monorepo. When we’re rebuilding the standalone history, these commits are restored from the commit message.

Maintaining forks of standard tools is unappealing. It would be better if I could ditch the fork. I could instead implement this custom functionality separately, built on top of git fast-export and git fast-import. So, I ran an experiment with Claude Code. I asked Claude to extract my particular changes around commit signature stashing-and-restoring into standalone Python scripts. Then, I reverted to the mainline git-filter-repo, inserting my scripts at the necessary steps. This worked well enough – signatures were restored from commit messages and rebuilt history properly. No more tooling forks required!

The tradeoff is some inefficiency from multiple passes instead of one, but this is unnoticeable for an operation run at most once-per-night from a CI server. I’ll take this inefficiency to gain the freedom from maintaining a fork for a standard tool.

Updated Tooling Approach Summary

Thanks to the new changes in mainline git, starting with 2.52.0, we have updated the monorepo tooling to:

  • Use mainline git-filter-repo and mainline git
  • Added support for remote preservation + restoration during distributed repository updates
  • Created Python scripts to stash signatures in commit messages and restore them from commit messages
  • Added these new scripts in relevant spots in monorepo-tools.

References

Share Your Thoughts

This site uses Akismet to reduce spam. Learn how your comment data is processed.