Git - サブモジュールがdirtyだと言われたので直した(コミットハッシュが"XXX-dirty")
Gitの本を出しておきながらアレだけど、サブモジュールのことをちゃんとしっかりはよく分かってない。ある日、いつものように git status したらサブモジュールに差分があるではないか。
※本記事でとりあげる例において、スーパープロジェクト*1から見たbats/lib/batsがサブモジュールである(GitHub - bats-core/bats-core: Bash Automated Testing Systemおよびその兄弟的なリポジトリたちを使っている)。
$ git status On branch pyrsia-1448 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) (commit or discard the untracked or modified content in submodules) modified: bats/lib/bats (modified content) no changes added to commit (use "git add" and/or "git commit -a")
差分を見ると
$ git diff diff --git a/bats/lib/bats b/bats/lib/bats --- a/bats/lib/bats +++ b/bats/lib/bats @@ -1 +1 @@ -Subproject commit c97b3a1b8644ddede18bff6bc035c12e9f50be78 +Subproject commit c97b3a1b8644ddede18bff6bc035c12e9f50be78-dirty
何もしてないのにdirtyだなんて失礼しちゃう・・・意味を調べた。
結論
サブモジュール内のファイルに変更を加えてしまっている(何もしてないわけがない)。
解決策 - 差分自体をなくす
git statusが言う通りgit restore / checkoutして消してしまえば良い。ただ、サブモジュール側のGitディレクトリに対する操作となるようなので、カレントディレクトリを変更してから実行するか下記のように環境変数を使って場所を指定すれば良い。
$ GIT_DIR=bats/lib/bats/.git git restore .
解決策 - 同様の差分はそもそも無視する
基本的にサブモジュールに変更を加えたいことはない(よね?)と思っているので、見つけたら上記のような対処をしてしまうのがいい気がするものの、無視する方法もあるらしい。
git diffやgit statusに--ignore-submoduleあるいは--ignore-submodules=dirty(この場合はdirtyのときだけ無視。none, all, dirty, untrackedのいずれかが使える)というオプションをつけるとサブモジュール内の変更は表示されない。
コマンド実行時にいちいち指定したくない場合、git config diff.ignoreSubmodules dirtyと設定変更しておけば毎度無視されるらしい。どういうモチベでオンにするのか正直よく分からないのはサブモジュール経験値が低いからか。
さらに、.gitmodulesファイルにignore = dirtyって書くという方法(↓例)もあるが、チーム全体で無視しよう!とはなかなかならないような?(基本的に無視に否定的…)
[submodule "bats/lib/bats"]
path = bats/lib/bats
url = https://github.com/bats-core/bats-core.git
ignore = dirty
だらだらと詳細
dirtyなんて失礼しちゃうと思ったところからの調べものメモ。差分が検知されたサブモジュールのGitディレクトリで改めてステータスを確認するとこうなった。
$ git status HEAD detached at c97b3a1 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: test/file_setup_teardown.bats no changes added to commit (use "git add" and/or "git commit -a")
該当ファイルの差分はこんな感じで、ローカルで末尾のスペースが削られていた。

これを見てひらめいた。多分、ShellCheck(ShellscriptのLintツール)をプロジェクト全体にかけちゃったからだ・・・。
やはりサブモジュールに変更を加えたいことってあんまりない気がするから見つけたらちゃんと確認するのが良さそう。やりたいタイミングとして思いつくのはサブモジュールとして依存しているライブラリのバグを踏んだときの動作確認とかかな?それ以外は正規の更新手順を踏むんじゃなかろうか。
ちなみに、スーパープロジェクトで実行したgit statusの結果(冒頭の例)だが、ファイル編集・削除を行った場合は modified: bats/lib/bats (modified content) 、追加すると modified: bats/lib/bats (untracked content) という表示になるようだ。dirtyにはこれらすべてが含まれ、 untrackedは追加のみを指すらしい。
参考文献 - 公式docありがとう
- Git - gitsubmodules Documentation
- Git - gitmodules Documentation
- Git - git-status Documentation
- Git - Environment Variables
*1:親にあたるリポジトリをこう呼ぶみたい。https://git-scm.com/docs/gitsubmodules/2.19.2