Gitのサブモジュールで発生した変更を親リポジトリで無視する2つの方法

Gitのサブモジュール内で何か変更などがあると親リポジトリ側でコミットができません。解決方法は主に2個あるようです。実際に再現させつつ、その解決方法を紹介します。
2019.04.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。サービスグループの武田です。

バージョン管理システムであるGitはソースコードなどをリポジトリという単位で管理します。さらにGitにはサブモジュールという機能があり、これを利用することで別のリポジトリを自身のリポジトリ内で再利用できます。

とても便利なサブモジュール機能なのですが、サブモジュール内で何か変更などがあると親リポジトリ側でコミットができません。たとえばサブモジュール内でビルドなどをして成果物が生成され、それが.gitignoreに書かれていない場合などが該当します。とはいえ、サブモジュールでそれをコミットするわけにもいきません。

解決方法は主に2個あるようです。実際に再現させつつ、その解決方法を紹介します。

検証環境

次のような環境で検証しました。

$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.14.3
BuildVersion:	18D109

$ git --version
git version 2.14.2

ローカルにリポジトリを準備する

まずは適当な作業ディレクトリに移動し、サブモジュールとなるsubmod-repoリポジトリを作成します。適当にREADMEと成果物を生成するビルドスクリプトを追加してコミットしておきます。

$ cd /tmp
$ mkdir submod-repo && cd $_
$ git init
$ echo '# submod-repo' > README.md
$ git add README.md
$ git commit -m 'init commit'
$ cat > build.sh <<EOF
#!/bin/bash
mkdir build
echo 'build success' > build/artifact.txt
EOF

$ chmod 755 build.sh
$ git add build.sh
$ git commit -m 'add build.sh'

続いてsubmod-repoリポジトリをサブモジュールとして利用するparent-repoリポジトリを作成します。

$ cd /tmp
$ mkdir parent-repo && cd $_
$ git init
$ echo '# parent-repo' > README.md
$ git add README.md
$ git commit -m 'init commit'

これで準備ができました。それではparent-repoリポジトリにsubmod-repoリポジトリをサブモジュールとして追加します。追加にはgit submoduleコマンドを使用します。

parent-repo

$ git submodule add "$(cd .. && pwd)/submod-repo"
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   .gitmodules
	new file:   submod-repo

$ git commit -m 'add submodule submod-repo'
$ git status
On branch master
nothing to commit, working tree clean

サブモジュールでファイルを追加してみる

サブモジュールで変更を発生させるため、サブモジュールとして追加したsubmod-repobuild.shを実行してみます。

parent-repo

$ cd submod-repo
$ ./build.sh
$ cd ..
$ git status
On branch 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)
  (commit or discard the untracked or modified content in submodules)

	modified:   submod-repo (untracked content)

no changes added to commit (use "git add" and/or "git commit -a")

$ git diff
diff --git a/submod-repo b/submod-repo
--- a/submod-repo
+++ b/submod-repo
@@ -1 +1 @@
-Subproject commit 18295850e3d1456cbda76626bf5b238b31a84394
+Subproject commit 18295850e3d1456cbda76626bf5b238b31a84394-dirty

もくろみどおりサブモジュールでuntrackedなファイルが追加されました。またgit diffを確認してみると dirty が付いています。直接的な解決策としてはサブモジュール内でコミットしてしまうことですが、今回のようにビルドした成果物をコミットしたくはありません。

--ignore-submodulesオプションを使用する

というわけで、ファイルが追加されてしまうことはしかたがないとして、親リポジトリで操作する際にサブモジュールの変更を無視できればよさそうです。これには--ignore-submodules[=<when>]オプションが利用できます。<when>にはnoneuntrackeddirtyallが指定できます。詳しくはドキュメントを参照してください。

Git - git-status Documentation

さっそくオプションを指定してコマンドを実行してみます。

parent-repo

$ git status --ignore-submodules=dirty
On branch master
nothing to commit, working tree clean
$ git diff --ignore-submodules=dirty

たしかに差分が出なくなりました!

.gitmodulesにignoreを追加する

オプションを指定すれば変更を無視できることがわかりました。しかし毎回オプションをつけるのは正直面倒です。また共同開発をしている場合、作業者全員に毎回オプションを付けろというのもなかなかのカオスです。そこで便利なのが.gitmoduleignore = dirtyの設定を追加する方法です。次のようにgit configコマンドを使用するか、直接ファイルを修正してもOKです。

parent-repo

$ git config --file=.gitmodules submodule.submod-repo.ignore dirty
$ git diff
diff --git a/.gitmodules b/.gitmodules
index 675a37d..c89ccaa 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
 [submodule "submod-repo"]
        path = submod-repo
        url = /tmp/submod-repo
+       ignore = dirty

修正したファイルをコミットして、あらためて差分を確認してみます。

parent-repo

$ git add .gitmodules
$ git commit -m 'add submodule ignore dirty'
[master a433fc6] add submodule ignore dirty
 1 file changed, 1 insertion(+)

$ git status
On branch master
nothing to commit, working tree clean

オプションを付けなくてもサブモジュールの差分が出なくなりました!素敵!

まとめ

サブモジュールで変更が出てしまうケースの解決方法をお届けしました。本音を言えば、こういった設定をしなくても済むのが理想ですね。自分が管理するリポジトリでは、コミットに含めないファイルなどは.gitignoreで指定しておきたいですね。