39
34

More than 5 years have passed since last update.

Git Submoduleについてまとめてみる

Last updated at Posted at 2017-08-04

はじめに

Git Submoduleについて自分で分かった範囲をまとめてみます。
同じような記事が沢山ありますが、バージョンが違うからか微妙に動作が異なっていたり、自分の思うようにいかない部分があったために記事にしました。

Gitのバージョンは、
Git Shell git version 2.11.0.windows.3
(GitHub for Windwos 3.3.4.0)
になります。

要件

  • 親プロジェクトで、子プロジェクト(サブモジュール)を使いたい。
  • 親プロジェクトでは、常に最新版の子プロジェクトを使う。
  • 親プロジェクト側からも子プロジェクトを更新したい。
  • 親プロジェクトは、公開する。
  • 子プロジェクトは、公開しなくても良い
  • 子プロジェクトは、それ単体でバージョン管理している。
  • 子プロジェクトをサブモジュールとして扱う。

3つ目の条件は、あまりよくないかもしれませんが個人のプロジェクトなので利便性を考えてこうしています。

構成

gitsubmodule Object diagram.png

親プロジェクトを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. 今までの状態を新しい変更と解釈して更新。つまり1を削除したものになる。 の2つの更新がかかるからです

プッシュ側のファイルを

 a
 b

受け取り側のファイルを

 a

としてプッシュすると

 a
 b

に更新した後

 a

を新しい更新として上書きする事になります。
結果、ファイルの内容は更新以前のままになります。

39
34
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
34