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
そして、submodule用のレポジトリtest_submoduleをpublic権限で用意しておきます1。submodule_master1, submodule_master2というファイルをmaster branchに別々にcommitしておきます。
こうして出来た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が取り込まれました。

次に、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プロジェクトは正しく更新されました。

けれども、親リポジトリであるtest_projectには、このコミットは反映されていません。

これをリモートリポジトリへ反映させるのに、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が反映されたコミットを参照するようになりました。

他のユーザーで操作してみる
次に、他のユーザーがsubmoduleを書き換えたときの挙動を実験してみます。
test_projectに他のユーザーを招待して、Cloneします。
$ 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のユーザーを変えるため、こちらを参考にユーザーを変更する設定をします(これは親のプロジェクトと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に別ユーザーでリモートリポジトリにコミットできました。

しかし、親プロジェクトの方にはコミットしていません。
$ 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を使ったテストを行いたいと思います。
-
public accessで良いのかは検討です
↩
