git filter-branch can do many things and at the same time update commit pointers so that the commit graph remains intact. One of them executes a command to edit all commit messages, such as the Gerrit commit-msg hook.
git filter-branch HEAD...HEAD~1 captures all commits in HEAD, but not HEAD ~ 1, which for merging, git -subtree is just a merge and squash.
git filter-branch --msg-filter accepts a run command, which receives a commit message sent to stdin and writes a new one to stdout. commit-msg scripts work on the file, so to write the original message to the file, you need to run the shell script, run the hook, and then send the file to stdout.
I put all this in a script to run before clicking on Gerrit:
# This command re-writes the history after doing a git subtree {pull|add}
source share