Git submoduleを複数人でいじったときの挙動を確かめるメモ

Gitのsubmoduleを個人で使ってはいるのですが、複数人で作業をすることになった時にどのような挙動になるのかが分からなくなってきたので、備忘録として実験してみたことをまとめてみます。
というのも、現在複数人で作業しているプロジェクトがあるのですが、ある一部のディレクトリをsubmoduleとして抜き出して、そのsubmoduleを公開し、大本のプロジェクトは開発プロジェクトとしてこれからも使っていきたいという経緯があります。Issueは残しておきたいし、今後とも開発時にはIssueを立ててsubmoduleの中身も変更していきたいからです。

# 確かめたいと思っていること

- branchの扱い
- 複数のプロジェクトでの使い回し方
- 複数のユーザーはどのようなコマンドを使わなければならないか

# 前提

リモートレポジトリの置き場として、自前のサーバに立てているGitLab CEを用います。
あるプロジェクト"test_project"と、submoduleにするプロジェクト"test_submodule"を作ります。
二人のユーザー, "John"さんと"Paul"さんが開発を行います。

# いざ実験

まず"test_project"をGitLab上(ホスト名はhoge,名前はjohn)で作り、cloneして"commit1_master"というファイルをmaster branchにcommitします。

```
$ git clone gitlab@hoge:john/test_project.git
$ cd test_project
$ touch commit1_master
$ git add commit1_master
$ git commit -m "[add] commit1 master"
$ git push origin master
```

当然、こんな感じに。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/c0a98e69-5ae3-07c4-6d8e-0843121bda48.png)

そして、submodule用のレポジトリ`test_submodule`をpublic権限で用意しておきます[^1]。`submodule_master1`, `submodule_master2`というファイルをmaster branchに別々にcommitしておきます。
[^1]: public accessで良いのかは検討です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/8296593f-8b61-96e7-e75f-2345fab63085.png)


こうして出来たsubmoduleを`test_project`に取り込みます。
`test_project`のディレクトリにて

```
$ git submodule add gitlab@hoge:john/test_submodule.git
$ ls test_submodule/
submodule_master1 submodule_master2 ("submodule_master2"までのcommitが反映されている)
$ git commit -m "[add] submodule"
$ git push origin master
```

これでこのリポジトリ内にsubmoduleが取り込まれました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/5bf46990-c4ef-b84a-7616-a6e437f32a21.png)

次に、submoduleとして取り込んでいるディレクトリで`submodule_master3`を作るという操作をしてみます。

```
$ cd test_submodule
$ touch submodule_master3
$ git add submodule_master3
$ git commit -m "[add] submodule_master3 commit @ submodule dir"
$ git push origin master
```

すると、`test_submodule`プロジェクトは正しく更新されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/8420b3ee-0f5a-91e8-f105-781773ea3513.png)

けれども、親リポジトリである`test_project`には、このコミットは反映されていません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/46bcbc51-a237-8c76-75ee-6edfa15c3df9.png)

これをリモートリポジトリへ反映させるのに、commitをします。

```
$ cd ..
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   test_submodule (new commits)

no changes added to commit (use "git add" and/or "git commit -a")
$ git add test_submodule
$ git commit -m "[mod] update test_submodule (submodule_master3)"
$ git push origin master
```
すると、`test_submodule3`が反映されたコミットを参照するようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/be691976-e2e3-85e9-e0ed-aa6b065bceac.png)

## 他のユーザーで操作してみる

次に、他のユーザーがsubmoduleを書き換えたときの挙動を実験してみます。
`test_project`に他のユーザーを招待して、Cloneします。

```shell-session
$ git clone gitlab@hoge:john/test_project.git
$ cd test_project
$ ls test_submodule
$
```
親プロジェクトをクローンしただけではsubmoduleの中身までは取れていません。

```
$ git submodule
-6c9751e0... test_submodule
```
のように、存在は知っています(test_project/.gitmodulesに書かれているので)
このsubmoduleを取ってくるには`submodule init`をしなければなりません。

```
$ git submodule init
$ git submodule update
$ ls test_submodule
submodule_master1  submodule_master2  submodule_master3
```
と、無事に取ってくることができました。

ここで、個別のプロジェクトでgitのユーザーを変えるため、[こちら](http://labs.opentone.co.jp/?p=4493)を参考にユーザーを変更する設定をします(これは親のプロジェクトとsubmoduleの両方で行わなくてはなりません)。ここではuser "paul"とします。

```
$ git config user.name "paul"
$ git config user.email "paul@hoge.com"
```

そして、submodule側でファイル`submodule_master4`を作ります。

```
$ touch submodule_master4
$ git add submodule_master4
$ git commit -m "[add] submodule_master4 by another user" 
$ git push origin master
```
これで、submoduleに別ユーザーでリモートリポジトリにコミットできました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255755/5d3f2b62-52e1-0107-5c56-0426e0e4f36e.png)

しかし、親プロジェクトの方にはコミットしていません。

```
$ cd ..
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   test_submodule (new commits)

no changes added to commit (use "git add" and/or "git commit -a")
```

そして、submoduleがアップデートされたかはPaulさんが親プロジェクトでsubmoduleの参照コミットが変わったことをコミットしないといけないということです。
これをJohnさんのローカルリポジトリで反映させるには

```
$ git submodule foreach git pull origin master
```
を実行します。

でも、これはきちんとsubmoduleにコミットした人が親プロジェクトにもちゃんとコミットする方が良さそうですね。他のユーザーがsubmoduleへ変更を加えたもので依存関係が崩れる可能性があるからです。
そこで、やはりブランチを切って作業をしたくなります。

## 親プロジェクトのブランチを切って作業する

親プロジェクトに何らかのfeature branchを切って、それぞれの人がsubmoduleをいじりながら作業をしてメインのbranchにmergeするのが一連の流れです。
基本的にはGitLabでIssueを立てて、そのIssueを行うBranchを切って作業を行います。ここでsubmoduleにコミットした場合の挙動をテストします。

ここでは、Johnさんが`feature_john`ブランチを、Paulさんが`feature_paul`ブランチを切って親プロジェクト、及びsubmodule中のファイルを変更し、先にJohnさんが`master`ブランチにMergeし、後にPaulさんがMergeする状況を作ってみます。

まず、Johnさんのローカルリポジトリでsubmoduleに対して`submodule_feature_john`ファイルを作り、`submodule_master3`の内容を変更する作業をします。

```
$ git checkout -b feature_john
$ cd test_submodule
$ touch submodule_feature_john
$ git add submodule_feature_john
$ git commit -m "[add] submodule_feature_john
$ git push
$ echo "edited by John" > submodule_master3 (同じファイルをいじってみてコンフリクトのテストもする)
$ git commit -m "[mod] submodule_master3 by John"
$ git push
$ cd .. (親プロジェクトに戻る)
$ git commit -m "[mod] submodule dir @ feature_john"
$ git push origin feature_john
```

次に、Paulさんも別の作業をしたいとします。

```
$ git checkout -b feature_paul
$ cd test_submodule
$ touch submodule_feature_paul
$ git add submodule_feature_paul
$ git commit -m "[add] submodule_feature_paul
$ git push
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'gitlab@hoge:john/test_submodule.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
```
この時点でコンフリクトが起きてしまいました。それもその筈。submodule上ではmasterブランチで作業していることになっているからです。
そうするとなると、submoduleに対してそれぞれfeatureブランチを切って作業しなければならないのでしょうか。完全に二度手間です。
Issueとの紐付けのために親プロジェクトのブランチを切って作業を行う時に
「このIssueを解決するのには、ぶら下がっているsubmoduleもいじらないとなぁ」
となることは多々あると思うのですが、その時にsubmodule側でもbranchを切らなければならないのは混乱がありそうな気がするのです。
それか、submoduleの方でもIssueを立てた方が良いのか。

# 結論

submoduleをいじる前提でのプロジェクト開発においては、複数人でBranchを切って作業するのには向かない

次は、subtreeを使ったテストを行いたいと思います。