はじめに
Git Submoduleについて自分で分かった範囲をまとめてみます。
同じような記事が沢山ありますが、バージョンが違うからか微妙に動作が異なっていたり、自分の思うようにいかない部分があったために記事にしました。
Gitのバージョンは、
Git Shell git version 2.11.0.windows.3
(GitHub for Windwos 3.3.4.0)
になります。
要件
- 親プロジェクトで、子プロジェクト(サブモジュール)を使いたい。
- 親プロジェクトでは、常に最新版の子プロジェクトを使う。
- 親プロジェクト側からも子プロジェクトを更新したい。
- 親プロジェクトは、公開する。
- 子プロジェクトは、公開しなくても良い
- 子プロジェクトは、それ単体でバージョン管理している。
- 子プロジェクトをサブモジュールとして扱う。
3つ目の条件は、あまりよくないかもしれませんが個人のプロジェクトなので利便性を考えてこうしています。
構成
親プロジェクトをParentとし、子プロジェクトをChildとします。
リポジトリの構成は、特に変哲もない図の上のようにしました。
子プロジェクトは、公開しなくても良いので図の下にして試してみましたが、ファイルの整合性がおかしくなるのであきらめました。
詳しくは、後述します。
それぞれのディレクトリとファイルの構成は、
Child
child.txt
Parent
parent.txt
のように、単純にディレクトリに1つのテキストファイルが存在するだけです。
child.txtは、
child
parent.txtは、
parent
と書いてあるだけです
リポジトリを作る
Childディレクトリに移動して、
# Child
git init
git add .
git commit -m "first"
git remote add origin <ChildRemoteURL>
git push -u origin master
を実行し、Parentディレクトリに移動して、
# Parent
git init
git submodule add <ChildRemoteURL>
git add .
git commit -m "first"
git remote add origin <ParentRemoteURL>
git push -u origin master
を実行します。
こうするとParentは、
Parent
.gitmodules
parent.txt
Child
child.txt
になります。
既存の記事には、
git submodule init
git submodule update
が必要と書いてありますが、現状では必要ないようです。
子プロジェクトの更新を取り込む
次に、子プロジェクトの更新を取り込む場合の操作を試してみます。
下準備として、Childリポジトリで、child.txtを
child
1 on child
と更新し、以下を実行します。
# Child
git add .
git commit -m "1 on child"
git push
Parentリポジトリに移り、以下を実行します。
# Parent
git submodule foreach git fetch
git submodule foreach git merge origin/master
これでParent配下のchild.txtが最新版に更新されます。
ここで上記のコマンドの代わりに、
# Parent
git submodule update --remote --merge
でも可能です。
また、更新するだけでならば以下のコマンドでも可能です。
# Parent
git submodule update --remote
git submodule update --remote
でもchild.txtの最新版を取り込むことが出来ます。
ただし、このコマンドの場合は、Parent配下のChildがDetached HEAD
になってしまいます。
Parent側からサブモジュールを更新する場合は、Detached HEAD
になるとひと手間かかります。
サブモジュールを参照するだけの場合は、このコマンドで十分です。
ここまでの操作で、ローカルのみ更新しました。
リモートも更新するために以下を実行します。
# Parent
git add .
git commit -m "update child1"
git push --recurse-submodules=check
親プロジェクト側から子プロジェクトを更新する
Parentリポジトリ配下のchild.txtを以下のように編集します。
child
1 on child
2 on parent
Parentリポジトリで以下を実行します。
# Parent
git submodule foreach git add .
git submodule foreach git commit -m "2 on parent"
git submodule foreach git push
この時点でリモートのChildが更新されます。
Parentのリモートも更新するために以下を実行します。
# Parent
git add .
git commit -m "update child 2"
git push --recurse-submodules=check
最後のChildのローカルを更新するために、Childリポジトリで以下を実行します。
# Child
git fetch
git merge origin/master
Detached HEAD
でのプッシュ
git submodule update --remote
を使ってサブモジュールを更新していた場合、Parent配下のChildがDetached HEAD
になっています。
この状態で、child.txtを更新してしまった場合、ParentからのChildへのプッシュが拒否されます。
その場合は、以下のコマンドなどでプッシュすることが出来ます。
# Parent
git submodule foreach git status #HEADのリビジョン名(SHA-1)を調べる
git submodule foreach git checkout master
git submodule foreach git merge <HEADのSHA-1> #rebse
git submodule foreach git push
最後に
以上が、サブモジュールの使い方の概要だと思います。
この記事では、全てリポジトリのルートディレクトリで操作するためにgit submodule foreach
を使っています。
サブモジュールのルートディレクトリ(上記の記事の場合はParent/Child)に移動すれば、submodule foreach
なしのコマンドが使えます。
複数のサブモジュールを使っている場合などは、こちらで操作しなければいけないこともあるでしょう。
コマンド
-
git push --recurse-submodules=check
配下のサブモジュールがプッシュされていないとメッセージを表示しプッシュが中断されるようになる。 -
git submodule foreach
配下の全てのサブモジュールに対して、指定したコマンドを実行する -
git submodule update
サブモジュールを、登録されているコミットIDで更新する -
git submodule update --remote
サブモジュールを、サブモジュールのリモート追跡ブランチ (remote-tracking branch)で更新する -
git submodule update --remote --rebase
サブモジュールの現在のブランチを、リモート追跡ブランチにリベースする -
git submodule update --remote --merge
サブモジュールの現在のブランチとリモート追跡ブランチをマージする
non-bareリポジトリにプッシュ
サブモジュールには直接関係ないのですが、図に示した下の構成をしないほうが良い理由なので記述します。
デフォルトの設定では、non-bareリポジトリにプッシュすると、全て拒否されます。
そこでプッシュを受け入れるリポジトリに、git config receive.denyCurrentBranch ignore
を設定しなければいけません。
設定すればプッシュを受け入れて履歴が更新されますが、ファイルの内容が更新されません。(されていないように見えます。)
何故更新されないかと言うと、
- プッシュされた状態にファイルを更新。
- 今までの状態を新しい変更と解釈して更新。つまり1を削除したものになる。
の2つの更新がかかるからです
プッシュ側のファイルを
a
b
受け取り側のファイルを
a
としてプッシュすると
a
b
に更新した後
a
を新しい更新として上書きする事になります。
結果、ファイルの内容は更新以前のままになります。