コミットの粒度を残して、email だけを揃える

リポジトリを正式運用に向けて設定する過程で、author 欄に何を入れるかを詰める段があった。Git クライアント側の user.email をどう揃えるか、GitHub 側の email privacy をどう扱うか、commit に紐づける email を noreply 形式にするか組織の表記にするか。いくつかの組み合わせを試しているあいだに、何件かの commit が積まれていた。

方針として、commit の author 欄は GitHub の noreply 形式に揃えることにした。設定を切り替えても、すでに積まれた commit の author 欄は遡及しない。残った数件をどう扱うかが、整え作業の最後に残った。

pull request も fork もまだ無い段階で、履歴に手を入れたときの外部への影響は想定から外せる状況だった。それでも、author 欄の過去分をどう扱うかには、二つの筋があった。

粒度のほうを残す

過去分を1つの squash commit にまとめる、というのがいちばん手早い。履歴を1コミットに圧縮してしまえば、方針と揃わない表記が出る面も1つだけになる。ただ squash で進めると、コミット単位の判断の痕跡、レビューでのやり取り、誰が何をどの段階で積んだかという記録も同時に畳まれる。author 欄の表記を揃えることと、コミットの粒度を残すことは、両方取れるなら両方取りたい。数件の commit だったので作業量はどちらの筋でも変わらず、squash で進めるべき作業上の理由が他にあるかを一度確認したが、特に無かった。粒度を残す筋を選んだ。

author と committer の email だけを差し替える道具として、git-filter-repo--mailmap を使う。mailmap は旧表記と新表記の対応を行単位で書いた設定ファイルで、filter-repo はそれに沿って履歴全体を書き直す。コミット単位の粒度を保ったまま、該当箇所の値だけが変わる。

実行の前に、まずリポジトリに記録されている author/committer の email の分布を一覧で出した。この分布を見ずに mailmap を書き始めるのは、できなかった。どの表記がどれくらい混ざっているのか分からないまま書いたら、対象を取り逃すか、対象外のものまで巻き込むか、どちらかを踏む気がした。出してみると、書き換えたい表記は想定どおりで、mailmap に書くべき行は思っていたより少なかった。

書き換えの最中、中間状態が公開面に出続けるのは避けたかった。対象リポジトリを一時的に private に切り替え、ローカルには作業用の clone と、戻すためのバックアップ用の clone を並べて置いた。被リンクへの影響は宙に浮いたままだったが、中途半端な履歴が見え続けるよりは、短時間だけ閉じて戻すほうが副作用は小さい、と判断した。

New Name <123456+user@users.noreply.github.com> <old@example.com>

mailmap にはこの形の行だけを書き、git filter-repo --mailmap に渡す。filter-repo が走った後、remote が自動で外されているのを再設定して、push する手前で一度止めた。止めたのは、方針と揃わない表記が残っていないことと、意図しない name や email まで巻き込んでいないことの両方を、自分の目で確かめたかったからだ。git log --all で author/committer を並べて grep をかける。なにも出ない。filter-repo が変更された ref の一覧を .git/filter-repo/changed-refs に残すので、pull request 由来の参照(refs/pull/*/head)がそこに含まれていることも見ておく。

確認が済んだ時点で force push した。ブランチとタグ、両方を更新する。公開面に戻って、commit 一覧、個別のコミットページ、blame(どの行を誰がいつ書いたかを辿るビュー)、API など、表記が出る面を順に見た。書き換え前の表記が残っている面はなかった。private を解除した。

書き換えが及ばない範囲

force push で参照グラフから外れたコミットが、そのまま GitHub から消えるとは限らない。cached views、clones、forks、pull requests。これらの経路には、旧 SHA を持ったコミットがしばらく残り得る。

このリポジトリには fork も pull request も無かったので、その経路は最初から考えずに済んだ。手が届かないのは、cached views と、もし誰かが手元に clone を取っていた場合の作業コピーのほうだった。

cached views については、GitHub のサポートが purge に動くのは sensitive data──流出すれば実害が出る類──に限られる。今回の対象は、その基準には届かない。完全に消すことよりも、どこに何が残り得るかを把握しておくことに比重を置いた。書き換え前の代表的な旧 SHA を、いくつか手元のメモに残してある。

今回の書き換えが成り立ったのは、commit SHA に依存する外部参照、署名付き commit、active な open pull request が無いリポジトリだったからでもある。SHA を変えること自体が他の運用に波及する場所では、書き換えの是非そのものから組み立て直すことになる。

書き換え後の履歴は、標準的な GitHub のビューからは方針と揃わない表記が見えない状態で、コミットの粒度はそのまま残っている。

川上 弘
kawakamisekkei / 株式会社川上設計