git が私の push を拒んだ日

ある日、git は私の作業を保存させることを単純にやめてしまった。私の push は、それまで一度も見たことのないサーバーエラーで失敗した——マージ衝突でもなく、権限の問題でもなく、ただ向こう側からのにべもない拒絶だった。私は何週間も気分よくコミットを続けてきたのに、いまや一日のうちで最もありふれたコマンドが完了しなくなったのだ。

原因は git のバグではなかった。原因は、私がこの何週間か、そこにあるべき理由のない数千ものものを静かにリポジトリに詰め込み続け、それがついに持ち上げるには重くなりすぎたことだった。

通らなかった push

最初、私はまぐれだろうと思った——ネットワークのしゃっくり、たまたま機嫌の悪いサーバー。だが、それは確実に、毎回起き続けた。リポジトリに積み上がったかさが原因のようだった。push は毎回まったく同じかたちで失敗し続け、いくら再試行しても、一時的なものではなく構造的な問題は直りようがなかった。

ようやくリポジトリに実際に何が入っているのかを見たとき、その答えは恥ずかしいものだった。コード——リポジトリが本来そのために存在するもの——は、そのうちのごく少数派だった。圧倒的な大半はデータだった。数千もの生成ファイル、キャッシュ、コンパイル成果物が、一つひとつ忠実に追跡され、バージョン管理され、あらゆる操作で一緒に運ばれていたのだ。

リポジトリはいかにして静かに倉庫になるか

ギガバイト単位のデータをバージョン管理に入れようと決める者はいない。それは、いちいちもっともらしく見える一歩ずつ起きていく。いくつかファイルを生成すると、それらがプロジェクトフォルダに着地する。add コマンドを、たぶん少し不注意に走らせると、それらも一緒についてくる。動く。何も文句を言わない。だからまた同じことをし、また同じことをし、リポジトリは引き出しが雑然としたものをため込むのと同じやり方でデータをため込んでいく——見えないままに、それが閉まらなくなる日まで。

git が私の push を拒んだ頃には、私は数千ものデータファイルを追跡していた。私はそれらをバージョン管理に置くという意識的な選択をしたことは一度もなかった。私はただ、それらを締め出すという選択を一度もしなかっただけだった。

データとコードは正反対のものを求める

両者を混ぜることが害になる理由は、それらがまったく異なる理由でバージョン管理されるからだ。コードは小さく、意味があり、あなたが一行ずつ見たいと思うかたちで変化する——git はまさにそのために作られている。データは大きく、不透明で、まるごと変わりがちだ。その一行ずつの差分はほとんど何も教えてくれず、その過去のすべてのバージョンを永遠に保存することはほぼ無駄だ。git は最初の仕事には素晴らしい。その標準的な履歴は、二つ目の仕事にはしばしば不向きだ——少なくともデータが大きくなったり、絶えず変わったりするようになれば。

両者を一緒にすると、両方の最悪を手に入れる。あなたの履歴は、決して意味あるかたちで読むことのできない巨大な変更で肥大し、すべての clone と push がそれをまるごと引きずり回し、そして小さく貴重なもの——本物のソース——は、大半がバラスト(重し)であるリポジトリの中に埋もれてしまう。

修正:あなたが飛ばした境界線を引く

修復は概念的にはごく単純で、少しばかり退屈だった。git に対して、明示的に、何を追跡しないかを伝えるのだ。生成ファイル、キャッシュ、コンパイル成果物——そのすべてが除外され、リポジトリは本来ずっとそうあるべきだった姿に戻った。すなわち、私が意図して書き起こしたもの——コード、設定 (config)、ドキュメント、テスト——の住みかであって、生成された出力のかさではない、という姿に。データは、大きく再生成可能なものが属するべき場所へ移った——必要に応じて生成されるか、大きなファイル向けに作られたどこかに保存されるかして、しかし二度とソースであるかのように一行ずつバージョン管理されることはない。

push は一発で通った。修正は気の利いたものではなかった。それはただ、初日に引いておくべきで、一度も引いてこなかった境界線だった。

その下にある習慣:ソースか派生か

長く残る教訓は、実は git についてのものではない。それは、プロジェクトが生み出すあらゆるファイルについて、私がいまや問うようになった問いだ。私はこれを書き起こしたのか、それとも私のコードがこれを生成したのか。これは ソース——人間が書いた、取り替えのきかないもの——なのか、それとも 派生——ソースをもう一度走らせれば再現できる出力——なのか。

ソースは一般にバージョン管理に属する。大きい、あるいはかさばる派生ファイルはたいてい属さない——それらは再現性やワークフローが本当に必要とするときにのみコミットされるべきで、理想的にはいつでもソースから再現可能であり続けるべきだ。手のつけられない混沌へと腐っていくリポジトリの多くは、その区別を見失うことでそうなる——派生したものを貴重であるかのように扱い、いつでも再生成できるものを、決して取り替えのきかないものの隣に積み上げさせてしまうことで。

あの日、git は壊れたのではなかった。それはただ、私がソースと出力をそれぞれ別の場所に保つことをやめてしまっていたこと、そして一つのツールに、それが決してそのために作られたのではない仕事をさせ続けてきたことに、ついに私を気づかせただけだった。

— シグナルもリターンもなし、投資助言ではない。