前回の記事で、submoduleをサブディレクトリに置いたプロジェクトに対してブランチを切って作業するのは難しいという結論になりましたので、今回はもう一つの手法であるサブディレクトリを別プロジェクトで運用する方法である、subtreeを使って、検証してみたいと思います。
前提
今回はtest_projectという親プロジェクト内にsubtreeを入れてみたい。
リモートリポジトリを視覚化するのに、自前のサーバに入れているGitLab CEを用いました。
作業者はJohnさん(GitLabでは水色アイコン)とPaulさん(GitLabでは赤色アイコン)の二人を仮定します。
実験
subtreeの生成・紐付け
まず、新規リモートリポジトリtest_subtreeをGitLabで作ります(gitlab@hoge:john/test_subtree.gitとします)。
空ファイルsubtree_master1も作って置きました。

そして、Johnさんのローカルリポジトリにcdして
$ git remote add test_subtree gitlab@hoge:john/test_subtree.git
$ git remote
origin
test_subtree (リモートリポジトリが追加されたことを確認)
$ git subtree add --prefix=subtree_dir test_subtree master
$ ls
commit1_master subtree_dir/ test_submodule/ (subtree_dirが生成される)
$ ls subtree_dir
subtree_master1 (中身もある)
$ git push origin master
リモートリポジトリにpushすると、GitLabの方ではこのように、subtreeがあたかもただのサブディレクトリのように見えます。

次に、Paulさんのローカルリポジトリでtest_projectをpullしてみます。
$ git pull
$ ls
commit1_master subtree_dir/ test_submodule/
$ ls subtree_dir
subtree_master1
となり、正しく反映されています。
masterブランチで編集する
再びJohnさんのローカルリポジトリに移って、新たにsubtreeとして登録したディレクトリに新しい空ファイルsubtree_master2を作ってみます。
$ cd subtree_dir
$ touch subtree_master2
$ git add subtree_master2
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: subtree_master2
$ cd .. (前のディレクトリに戻ってみても)
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: subtree_dir/subtree_master2
(同じ内容がでます)
subtreeのディレクトリだけでなく、親レポジトリでも同様のステータスが表示されます。つまり、本当のサブディレクトリとして扱われているということですね。
$ git commit -m "[add] subtree_master2 by John"
$ git push origin master
リモートリポジトリに反映させた親プロジェクトのDetails画面がこちらです。

しかし、これはsubtreeのプロジェクトには反映されていません。

これを反映させるには
$ git subtree push --prefix=subtree_dir test_subtree master
を実行します。すると、subtreeのリモートリポジトリにも反映されました。

ここで、Johnさんでもう一つのファイルsubtree_master3を作り、親プロジェクトtest_projectにだけpushします。
$ touch subtree_dir/subtree_master3
$ git add subtree_dir/subtree_master3
$ git commit -m "[add] subtree_master3 by John
$ git push origin master
次に、Paulさんのローカルリポジトリでpullってみます。
$ git pull origin master
$ ls subtree_dir
subtree_master1 subtree_master2 subtree_master3
このように、subtreeにpushせずとも、親プロジェクトの内容が保持されています。
Branchを切ってみる
次に、親プロジェクトにて各ユーザーが開発ブランチを切って作業するテストをしてみます。
まずはJohnさんのローカルリポジトリにてfeature branchを切って、subtree_dirにファイルを置いてコミットし、pushします。また、コンフリクトの検証のために、subtree_master1に手を加えてみます。
$ git checkout -b feature_john
$ touch subtree_dir/subtree_feature_john
$ git add subtree_dir/subtree_feature_john
$ git commit -m "[add] subtree_feature_john"
$ echo "Edited by John" > subtree_dir/subtree_master1
$ git add subtree_dir/subtree_master1
$ git commit -m "[mod] subtree_master1 by John"
$ git push origin feature_john
test_projectのリモートリポジトリに反映されています。

Paulさんのローカルリポジトリでも同様な作業を行います。
$ git checkout -b feature_paul
$ touch subtree_dir/subtree_feature_paul
$ git add subtree_dir/subtree_feature_paul
$ git commit -m "[add] subtree_feature_paul"
$ echo "Edited by Paul" > subtree_dir/subtree_master1
$ git add subtree_dir/subtree_master1
$ git commit -m "[mod] subtree_master1 by Paul"
$ git push origin feature_paul
こちらもtest_projectのリモートリポジトリに反映されています。

Mergeしてみる
Johnさん,Paulさんふたりとも作業が終わりました。
ここではtarget branchをmaster branchにして、2つのfeature branchをそれぞれMerge requestを出していきます。
まずはJohnさんから

Merge requestを作って

Merge出来ました。
次に、Paul

Merge requestを作ります。

Conflictが起きているので解消させて

このプロジェクトの中では、まるでただのsub directoryのように扱える事がわかりました。
このようにして開発したmaster branchの中身をsubtreeのリポジトリに反映させます。
$ git subtree push --prefix=subtree_dir test_subtree master
subtreeへのコミットログについて
subtree_dir/subtree_master4 -> project_master1 -> subtree_dir/subtree_master5とファイルを作ってコミットするという作業をして、最後にリモートリポジトリへpushし、subtreeにもpushします。
$ touch subtree_dir/subtree_master4
$ git add subtree_dir/subtree_master4
$ git commit -m "[add] subtree_master4"
$ touch project_master1
$ git add project_master1
$ git commit -m "[add] project_master1"
$ touch subtree_dir/subtree_master5
$ git add subtree_dir/subtree_master5
$ git commit -m "[add] subtree_master5"
$ git push origin master
$ git subtree push --prefix=subtree_dir test_subtree master
この結果、親プロジェクトのリポジトリのコミットログは以下のようになり

subtreeのリポジトリのコミットログは以下のようになりました。

subtreeにpushするときに、自動的にfilter-branch的なことをやっているのか、subtree_dirに関連するコミットのみ残りました。
まとめ
subtreeでは一つのプロジェクト内で複数人が開発するときにはsubmoduleのように他人の変更に影響を受けずに、ただのサブディレクトリとして扱うことができる。
今回の狙いは
一つのプロジェクトでIssueを立てて開発していき、完成させたサブディレクトリを公開・他のプロジェクトに流用させたい
というものでした。基本的には他のプロジェクトからこのサブディレクトリを編集することはないので、プロジェクトのMaintainerがリリースするときにsubtreeにpushするということを行えば、他のDeveloperに複雑な作業を強いることもなく開発してもらえそうです。
参考

