サブモジュールとは
複数のプロジェクトを「別々に管理したい」が、「それぞれのプロジェクトがお互いのバージョンに依存しているため、紐付けは行いたい」という場合に使用される。つまり、サブモジュールを利用することで、利用しているリポジトリの外部のリポジトリに対する依存を簡単に管理できるようになる。
サブリポジトリはそれ自身が通常のリポジトリであるため、サブモジュール内に入り、自身のリポジトリに対してGit操作を行う際は、通常のリポジトリでの操作と何ら変わりがない。そのため、以下の説明では「リポジトリA」内に「リポジトリB」をサブモジュールとして追加した場合、「リポジトリA」を親リポジトリ、「リポジトリB」をサブリポジトリと呼ぶことにする。
サブモジュールは親リポジトリと紐づいているが、親リポジトリから独立したサブモジュール自身のコミット履歴を保持している。
親リポジトリの生成
parent
ディレクトリを生成する。
$ mkdir parent
parent
ディレクトリにリポジトリを生成する。
$ cd parent
parent/$ git init
リポジトリが生成された(.git
ファイルの存在)ことを確認する。
parent/$ ls -a
. .. .git
index.html
ファイルを生成して、main
ブランチとしてコミットする。
parent/$ git branch
* master
parent/$ git branch 'main'
parent/$ git branch
main
* master
parent/$ git switch main
parent/$ touch index.html
parent/$ ls
index.html
parent/$ git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
index.html
nothing added to commit but untracked files present (use "git add" to track)
parent/$ git add .
parent/$ git commit -m 'first commit'
parent/$ git status
On branch main
nothing to commit, working tree clean
サブモジュールにするリポジトリの生成
GitHubを利用して、親リポジトリparent
のサブモジュールにする予定のリモートリポジトリ sub_module
を生成する。
具体的には、まずローカルリポジトリとして sub_module
を生成する。
続いて sub_main
ブランチ上で index.html
を作成した状態を sub first commit としてコミットし、そのコミットをリモートリポジトリへプッシュする。
parent/$ mkdir sub_module
parent/$ cd sub_module
parent/sub_module/$ git init
parent/sub_module/$ touch index.html
parent/sub_module/$ git add .
parent/sub_module/$ git commit 'sub first commit'
parent/sub_module/$ git branch sub_main
parent/sub_module/$ git switch sub_main
parent/sub_module/$ git branch
master
* sub_main
parent/sub_module/$ git remote add origin https://github.com/<GitHubのユーザ名>/sub_module.git
parent/sub_module/$ git push origin sub_main
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 282 bytes | 282.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/<GitHubのユーザ名>/sub_module.git
a33abc6..f733b3a sub_main -> sub_main
この時点で以下の状態になっている。
(repository: parent)
(branch: main)
parent
├ index.html
├ sub_module/
(repository: sub_module)
(branch: sub_main)
sub_module
├ index.html
サブモジュールの生成 / $ git submodule add <URL>
生成したリモートリポジトリ sub_module
を、ローカルリポジトリの parent
のサブモジュールとして登録する。
parent/$ git submodule add https://github.com/<GitHubのユーザ名>/sub_module.git
Cloning into '/Users/<GitHubのユーザ名>/Documents/parent/sub_module'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (3/3), done.
リポジトリ parent
内に、サブモジュール sub_module
が生成されていることを確認。
parent/$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: .gitmodules
new file: sub_module
parent
リポジトリに生成したサブモジュールに対する変更が差分として扱われており(git add
された状態だった)、sub_module
ディレクトリには .gitmodules
ファイルが生成されていることがわかる。
.gitmodules
ファイルの中身を見てみると、「サブモジュールとして指定したリポジトリのURL」と「それを取り込んだディレクトリ名」が記載されていた。
parent/$ cat .gitmodules
[submodule "sub_module"]
path = sub_module
url = https://github.com/<GitHubのユーザ名>/sub_module.git
(以降 cd
によるディレクトリの移動を省略しています)
parent/sub_module/$ git status
On branch sub_main
Your branch is up to date with 'origin/sub_main'.
サブモジュール(sub_module
)に対しては変更を加えていないため、sub_module
リポジトリにおいては、ブランチが最新のコミットを参照していることがわかる。また、親リポジトリに対して独立してリポジトリ(sub_module
)が存在していることもうかがえる。
親リポジトリの変更分を second commit としてコミットする。
parent/$ git commit -m 'second commit'
parent/$ git status
On branch main
nothing to commit, working tree clean
サブモジュールの情報を確認する / $ git submodule
/ $ git submodule status
親リポジトリがサブモジュールのどのブランチを参照しているかなどを知ることができる。
$ git submodule
コマンドは、リポジトリ内にサブモジュールが複数ある場合のサブモジュールの一覧や状態に関する情報を表示することができる。
parent/$ git submodule
a33abc6f5aff689c0914cb1b5d77739312d162ff sub_module (heads/sub_main)
$ git submodule status
コマンドは、サブモジュールが参照しているコミットIDと、そのコミットがどのブランチに属しているかを表示することができる。
parent/$ git submodule status
a33abc6f5aff689c0914cb1b5d77739312d162ff sub_module (heads/sub_main)
親リポジトリで起きた変更とサブモジュール(サブモジュールのブランチに変更がない場合)
ここで、親リポジトリの main
ブランチを third commit コミットまで進めてみる。index.html
を編集する。
parent/$ git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: index.html
no changes added to commit (use "git add" and/or "git commit -a")
parent/$ git add .
parent/$ git commit -m 'third commit'
parent/$ git status
On branch main
nothing to commit, working tree clean
サブモジュールで起きた変更と親リポジトリ
今度は、サブモジュールの sub_main
ブランチを sub second commit コミットまで進めてみる。サブモジュールの index.html
を編集する。
parent/sub_module/$ git status
On branch sub_main
Your branch is up to date with 'origin/sub_main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: index.html
parent/sub_module/$ git add .
parent/sub_module/$ git commit -m 'sub second commit'
parent/sub_module/$ git push origin sub_main
parent/sub_module/$ git log --oneline
f733b3a (HEAD -> sub_main, origin/sub_main) sub second commit
a33abc6 sub first commit
この時、親リポジトリからはどのように見えるのかを確認してみる。
parent/$ git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: sub_module (new commits)
no changes added to commit (use "git add" and/or "git commit -a")
parent/$ git submodule status
+f733b3a79c7b27452c3772a969358b68f22531d9 sub_module (heads/sub_main)
サブモジュールの変更前は以下のコミットIDを参照していたので、サブモジュールの変更が差分として表示されていることになる。
parent/$ git submodule status
a33abc6f5aff689c0914cb1b5d77739312d162ff sub_module (heads/sub_main)
つまり親リポジトリとサブモジュールの紐づけ情報とは、ローカルの状態そのもののことを指していることがわかる。
これをコミットする。
parent/$ git add .
parent/$ git commit -m 'ref sub_module_second'
親リポジトリで起きた変更とサブモジュール(サブモジュールのブランチに変更がある場合)
ここで、親リポジトリとサブモジュールのブランチとコミットの関係を整理する。
()内はサブモジュールのコミット。
-
first commit
- 親リポジトリ:
index.html
作成 - サブモジュール: 存在していない
- 親リポジトリ:
-
second commit(sub first commit)
- 親リポジトリ: 更新なし
- サブモジュール:
index.html
作成
-
third commit (sub first commit)
- 親リポジトリ:
index.html
にhello world
- サブモジュール: 更新なし
- 親リポジトリ:
-
ref sub second (sub second commit)
- 親リポジトリ: 更新なし
- サブモジュール:
index.html
にsub hello world
親リポジトリに test
ブランチを作り、一つ前のコミットを参照させる。
【変更前】 ref sub second (sub second commit)
【変更後】 third commit (sub first commit)
parent/$ git branch test
parent/$ git switch test
parent/$ git branch
main
master
* test
parent/$ git log --oneline
c33be2f (HEAD -> test, main) ref sub_module_second
03622c2 third commit
8cd9d33 second commit
ebea95c first commit
parent/$ git reset --hard 8cd9d33
HEAD is now at 8cd9d33 second commit
parent/$ git log --oneline
8cd9d33 (HEAD -> test) second commit
ebea95c first commit
この時、親ブランチの test
ブランチはサブモジュール sub first commit と紐づいているはずなので、サブモジュール内の index.html
からは sub hello world
の文字がなくなっていそうだが、実際にどうなっているかを見てみる。
parent/$ git status
On branch test
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: sub_module (new commits)
no changes added to commit (use "git add" and/or "git commit -a")
これらの結果からわかるのは、サブモジュールは sub second commit のままであり、自動的に親リポジトリの second commit と紐づく sub first commit の状態にはならなかったということである。
つまり親リポジトリにおいてブランチを移動しても、サブモジュールの内容は自動更新されない。このことからサブモジュールはあくまでも親リポジトリから独立して存在する別のリポジトリであることがわかる。
サブモジュールはあくまでも親リポジトリから独立して存在する別のリポジトリ
サブモジュールの更新 / $ git submodule update
この状態から、サブモジュールを sub first commit の状態にするには $ git submodule update
コマンドを実行する必要がある。
$ git submodule update
コマンドには --init
オプションがあり、ローカルリポジトリに初めてリモートリポジトリからサブモジュールをクローンする場合などは、リポジトリの初期化が必要なため --init
オプションをつける。
また、サブモジュールがさらにサブモジュールを持つ場合などには --recursive
オプションをつけることで再帰的に更新を行うことができる。
どちらも付けて不具合の原因になることはないため、毎回つけていても良い。
parent/$ git submodule update --init --recursive
Submodule path 'sub_module': checked out 'a33abc6f5aff689c0914cb1b5d77739312d162ff'
parent/$ git status
On branch test
nothing to commit, working tree clean
これで親リポジトリ test
ブランチが紐づくサブモジュールの sub first commit の状態になったことになる。
参考にしたサイトでも述べられていたが、ここがサブモジュールの挙動がわかりづらい原因と思われる。