Git subtreeを使って複数人開発してみるテストメモ

[前回の記事](https://qiita.com/sho0405/items/090580e9f18ad5317a54)で、submoduleをサブディレクトリに置いたプロジェクトに対してブランチを切って作業するのは難しいという結論になりましたので、今回はもう一つの手法であるサブディレクトリを別プロジェクトで運用する方法である、subtreeを使って、検証してみたいと思います。

# 前提
今回は`test_project`という親プロジェクト内にsubtreeを入れてみたい。
リモートリポジトリを視覚化するのに、自前のサーバに入れているGitLab CEを用いました。
作業者はJohnさん(GitLabでは水色アイコン)とPaulさん(GitLabでは赤色アイコン)の二人を仮定します。


# 実験

## subtreeの生成・紐付け

まず、新規リモートリポジトリ`test_subtree`をGitLabで作ります(`gitlab@hoge:john/test_subtree.git`とします)。
空ファイル`subtree_master1`も作って置きました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/d1775b9d-09eb-4b41-bb82-0303e8edd9ca.png)


そして、Johnさんのローカルリポジトリに`cd`して

```shell
$ 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があたかもただのサブディレクトリのように見えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/4e5765f1-14d6-3de0-6668-1e4c5bc79428.png)

次に、Paulさんのローカルリポジトリで`test_project`を`pull`してみます。

```shell
$ git pull
$ ls
commit1_master subtree_dir/ test_submodule/
$ ls subtree_dir
subtree_master1
```

となり、正しく反映されています。

## `master`ブランチで編集する

再びJohnさんのローカルリポジトリに移って、新たにsubtreeとして登録したディレクトリに新しい空ファイル`subtree_master2`を作ってみます。

```shell
$ 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画面がこちらです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/28ef32e9-d063-0caf-1076-16f675862e67.png)
しかし、これはsubtreeのプロジェクトには反映されていません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/00b2905f-885d-386d-08ef-7b0de53f3bef.png)
これを反映させるには

```
$ git subtree push --prefix=subtree_dir test_subtree master
```
を実行します。すると、subtreeのリモートリポジトリにも反映されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/982dd466-f185-f52c-0aa0-ecfa898d6930.png)

ここで、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`のリモートリポジトリに反映されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/28d1a1e9-369d-9766-fa77-beac27b15712.png)


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`のリモートリポジトリに反映されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/71591620-2a81-31d5-d3e8-d86d56a9d7c8.png)


## Mergeしてみる

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

まずはJohnさんから
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/68793cb2-50a0-2da6-f395-a8b2093e5075.png)
Merge requestを作って
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/420e6165-92a1-4a6f-5fa0-f5469a9ebfad.png)
Merge出来ました。

次に、Paul
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/075fb54e-b2ee-cdf9-0c85-909e1eb3a4bf.png)
Merge requestを作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/540c94c4-ca1d-acfd-4570-e8d3a4149657.png)
Conflictが起きているので解消させて
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/8c799401-dc6e-5531-4fae-d85700de9407.png)

Mergeしました。
その結果がこちら
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/9bedf904-6fe4-d003-0835-10ee1ddaa4b9.png)

このプロジェクトの中では、まるでただのsub directoryのように扱える事がわかりました。

このようにして開発したmaster branchの中身をsubtreeのリポジトリに反映させます。

```
$ git subtree push --prefix=subtree_dir test_subtree master
```
とすると、subtreeリポジトリに反映されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/3393fdea-198c-5b91-a2df-ef2af278b934.png)

## 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
```
この結果、親プロジェクトのリポジトリのコミットログは以下のようになり
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/fd80da88-4ccb-66ba-a5da-e2d562560d25.png)
subtreeのリポジトリのコミットログは以下のようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/0d8e6f27-5090-d992-25b7-85f83fbd9dea.png)
subtreeにpushするときに、自動的に`filter-branch`的なことをやっているのか、`subtree_dir`に関連するコミットのみ残りました。


# まとめ
subtreeでは一つのプロジェクト内で複数人が開発するときにはsubmoduleのように他人の変更に影響を受けずに、*ただのサブディレクトリとして扱うことができる*。

今回の狙いは

一つのプロジェクトでIssueを立てて開発していき、完成させたサブディレクトリを公開・他のプロジェクトに流用させたい

というものでした。基本的には他のプロジェクトからこのサブディレクトリを編集することはないので、プロジェクトのMaintainerがリリースするときにsubtreeにpushするということを行えば、他のDeveloperに複雑な作業を強いることもなく開発してもらえそうです。

# 参考
https://qiita.com/__mick/items/e027cc3941ac52743bd5