0
1

Git サブモジュール

Last updated at Posted at 2024-09-08

サブモジュールとは

複数のプロジェクトを「別々に管理したい」が、「それぞれのバージョンの紐付けは行いたい」場合に使用される。主として利用しているリポジトリが外部のリポジトリに依存している際にそれらを簡単に管理できるようにする。

サブリポジトリはそれ自身が通常のリポジトリであるため、サブモジュール内に入り、自身のリポジトリに対して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を利用して、サブモジュールにする予定のリモートリポジトリ 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

この時点で以下の状態になっている。

Screenshot 2024-09-09 at 6.51.28.png

parent
(repository: parent)
(branch: main)

parent
  ├ index.html
  ├ sub_module/
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 が生成されていることを確認。

Screenshot 2024-09-08 at 18.19.04.png

parent
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

Screenshot 2024-09-08 at 18.20.43.png

(以降 cd によるディレクトリの移動を省略しています)

sub_module
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 コマンドは、リポジトリ内にサブモジュールが複数ある場合のサブモジュールの一覧や状態に関する情報を表示することができる。

$ git submodule
parent/$ git submodule
a33abc6f5aff689c0914cb1b5d77739312d162ff sub_module (heads/sub_main)

$ git submodule status コマンドは、サブモジュールが参照しているコミットIDと、そのコミットがどのブランチに属しているかを表示することができる。

$ git submodule status
parent/$ git submodule status
a33abc6f5aff689c0914cb1b5d77739312d162ff sub_module (heads/sub_main)

親リポジトリで起きた変更とサブモジュール(サブモジュールのブランチに変更がない場合)

ここで、親リポジトリの main ブランチを third commit コミットまで進めてみる。index.html を編集する。

Screenshot 2024-09-08 at 18.14.24.png

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 を編集する。

Screenshot 2024-09-08 at 19.45.40.png

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 commitsub first commit
    • 親リポジトリ: 更新なし
    • サブモジュール: index.html 作成
  • third commit (sub first commit)
    • 親リポジトリ: index.htmlhello world
    • サブモジュール: 更新なし
  • ref sub second (sub second commit)
    • 親リポジトリ: 更新なし
    • サブモジュール: index.htmlsub hello world

Screenshot 2024-09-08 at 22.02.18.png

親リポジトリに test ブランチを作り、一つ前のコミットを参照させる。

【変更前】 ref sub second (sub second commit)
【変更後】 third commit (sub first commit)

Screenshot 2024-09-08 at 22.02.18.png

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")

Screenshot 2024-09-08 at 19.45.40.png

これらの結果からわかるのは、サブモジュールは 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

Screenshot 2024-09-08 at 21.52.17.png

これで親リポジトリ test ブランチが紐づくサブモジュールの sub first commit の状態になったことになる。

参考にしたサイトでも述べられていたが、ここがサブモジュールの挙動がわかりづらい原因と思われる。

参考

0
1
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
0
1