Hail2u

git subtreeの練習

Gitのサブモジュールでは面倒そうな、頻繁に更新される別のリポジトリを取り込む方法としてサブツリーマージを行うラッパーであるgit subtreeコマンドを使う練習を始めた。どちらかというと「参照する」要素の強いサブモジュールに対して、サブツリーは「切り分ける」や「取り込む」という感じなんじゃないかと理解している。全般的に間違ってそうで怖い。

「切り分ける」、つまりリポジトリのサブディレクトリを別のリポジトリにしたい場合は、単純なケースだと親にあたる方で.gitignore.git/info/excludeを使ってサブディレクトリを除外してやれば良い。でもこの場合、両方のリポジトリで関連した変更がある時にそれぞれのリポジトリでコミットしてやらないとならないので面倒くさい。

「取り込む」場合はサブモジュールが基本なわけだけど、他で作業して戻ってきてたりする必要があるし、サブモジュールの更新ほど面倒くさい作業はない。理解が足らないとミスもしやすいと思う。

サブツリーを使うと、親となるリポジトリでまとめてコミットしたり、子になるリモート・リポジトリを取り込んだサブディレクトリでシームレスに作業を行ったりしたりできる。そしてその後、サブツリーとして登録したリモート・リポジトリへ、サブディレクトリのファイルへのコミットだけを反映させることもできる。

$ git subtree push --prefix=subtree_dir/ subtree_origin master

まさにこれを目的としていた。つまり作業を一元化し、特定のディレクトリへ加わった変更だけを別のリポジトリに投げる、という感じ。親の方にプライベートなファイルが存在していたりして公開したくない(バージョン管理はしたい)が、サブディレクトリは普通に公開しておきたいとかそういうの。単一のリポジトリで普通に扱うので、ミスはしづらいような気がする。

リモート・リポジトリをサブツリーとして登録

git remote addgit subtree addを使う。

$ git remote add -f subtree_origin https://example.com/bob/foo.git
$ git subtree add --prefix=foo/ --squash subtree_origin master

取り込みたいリモート・リポジトリをsubtree_originとして登録し、サブディレクトリfoo/として取り込む。--squashを省略すると履歴を継承できるので、サブモジュールや上述のようなリポジトリのネストから移行する場合はつけない方が良さそう。履歴が読みづらくなるのがちょっとアレだけど、logコマンドに--date-orderオプションを付ければなんとかなる気がする(またはSourceTreeで)。リモートに反映させる時に死ぬので必ず--squashを付けるべきとした方が良さそう。

コミット

ファイルへ加えた変更のコミットはサブツリーのファイルだろうとそうでなかろうと普通にコミットするだけ。

$ git add file-a.txt
$ git add foo/file-b.txt
$ git commit -m 'Modified!'

単純にひとつのリポジトリとみなして操作すれば良い。

コミットをリモート・リポジトリへ反映

git pushgit subtree pushを使う。

$ git push origin master
$ git subtree push --prefix=foo/ subtree_origin master

これでoriginにはfile-a.txtfoo/file-b.txtへ加わった変更が、subtree_originへはfoo/file-b.txtへ加わった変更のみが反映される。


サブツリー関係はウェブで調べてもノイズが多くてよくわからなかった。古い情報も勿論混ざっているし、新しく書かれた記事でも古い情報を元にしていたり、混ぜこぜになっていたり。前提知識がなさすぎて取捨選択できなかったので、一旦全て忘れてとにかくgit subtree -hcontirib/subtree/git-subtree.txtだけを読んで、色々試してみて覚えることにした。

このウェブサイト全体のリポジトリをBitbucketでプライベートなリポジトリとして作り、ウェブログドキュメントCSSのリポジトリをsubtreeとして混ぜるみたいな形で色々試行錯誤してみて、なんとなく理解できたような気がする。git reset --hardgit push -fを数十回繰り返した価値はあった。

誰かが管理してるリポジトリはサブモジュールで特定のコミットに縛り、自分がメインで管理しているリポジトリはサブツリーでいじりやすいようにしておく、というのが良いのかな。他のリポジトリを取り込むという利用だけでなく、サンプルを置いたディレクトリをgh-pagesブランチを使ったサブツリーにして、GitHub Pagesの生成をシームレスに行うとかも出来そう。