rcmdnk's blog
Last update

Cyclic Pure Submodules

Gitのsubmoduleがいつもイマイチ良くわからなくなるので 自分なりのまとめ。

レポジトリにsubmoduleの追加

git submodule addで追加。

$ git submodule add [email protected]:rcmdnk/evernote_mail.git ./submodules/evernote_mail

addすると.gitmoduleというファイルがまだ無い場合は作られ、その中に

[submodule "submodules/evernote_mail"]
    path = submodules/evernote_mail
    url = [email protected]:rcmdnk/evernote_mail.git

の様にsubmoduleのレポジトリのURLに加えて現レポジトリ内での位置情報が組み込まれます。

.git/configの方にも

[submodule "submodules/evernote_mail"]
    url = [email protected]:rcmdnk/evernote_mail.git

として情報が組み込まれます。

その後、通常の更新と同様

$ git add .gitmodules submodules/evernote_mail
$ # or git add -A
$ git commit -m "Added submodule:evernote_mail"
$ git push

とすれば、.gitmoduleファイル、及び、上記で指定した submoduleのディレクトリが親レポジトリへ登録されます。

GitHub上ではsubmoduleはそのレポジトリへのリンクになってみえます。

ref: https://github.com/rcmdnk/scripts/tree/master/submodules

submoduleのあるレポジトリをcloneする

この親レポジトリ(scripts)を他でcloneすると、 .gitmoduleだけにevernote_mailがあり、 evernote_mailディレクトリの中身は空のママ。 .git/configにも記述がないまま。

これを取ってくるためには

$ git submodule init
$ git submodule update

または

$ git submodule update --init

をします。init.git/configへsubmoduleの情報を登録し、 updateで登録されてた状態にするためcloneしてきます(update --init==init+update)。

コマンドの最後に

$ git submodule update --init submodules/evernote_mail

の様にディレクトリ名を付ければそのsubmoduleだけに対して行います。

追記: 2013/12/04

$ git clone --recursive [email protected]:rcmdnk/scripts

の様に、--recursiveをつけるとclone後にgit submodule update --init が自動的に行われるので、すべて取ってきて特に問題ない場合は --recursive付きで一気に持ってこれます。

追記ここまで

追記: 2015/06/16

さらにsubmodule自体にもsubmoduleがあるような場合も全部 cloneしたりupdateしたりするようにしたい場合には

$ git submodule update --init --recursive

--recursiveのオプションを付けます。

追記ここまで

このupdateがいまいち何に対するアップデートなのか分からなくて混乱してしまいますが、

  • o 親レポジトリに登録されているsubmoduleのレポジトリ情報に基づきsubmoduleをアップデートする。

と言う意味でいいかと思います。

下に書くように、updateは決して

  • x submodule自身のレポジトリと比較して最新にアップデートする。

ではありません。 なので、何らかの変更をsubmodule内で直接する時は注意が必要で、 submoduleで変更した後、いきなり親ディレクトリで git submodule updateすると変更が全て上書きされて、 親ディレクトリに登録された状態、に戻されます。

submodule内で作業をした場合には、必ずadd/commitして、 さらに親ディレクトリからもaddしてsubmoduleの状態をきちんと更新しておく 必要があります。

submoduleの更新

submoduleのレポジトリが更新されてたりしても自動的には更新されません。

各submoduleの中で最新の状態にして、親レポジトリで再度git addする必要があります。

$ cd submodules/evernote_mail
$ git pull
$ cd -
$ git add submodules/evernote_mail
$ git commit -m "updated evernote_mail"

これで登録されているレポジトリ情報も最新のものになります。

また、submoduleを直接pullしたりするコマンドはないみたいですが、 foreachというコマンドがあって、これをすると全てのsubmoduleに対して同じコマンドを 打てるのでこれを使えば

$ git submodule foreach git pull

とすればOK。

また、親レポジトリをcloneした際に既にsubmoduleに更新がある場合、 submoduleがcloneされた後に、 以前のコミットの状態で無名ブランチ(detached HEAD)になってしまいます。

$ cd submodules/evernote_mail
$ git branch
* (detached from 7efd90a)
  master

なので、その状態で素直にgit pullすると

$ git submodule foreach git pull
Entering 'submodules/evernote_mail'
You are not currently on a branch. Please specify which
branch you want to merge with. See git-pull(1) for details.

    git pull <remote> <branch>

Stopping at 'submodules/evernote_mail'; script returned non-zero status.

の様なエラーが出てしまいます。 ので、もし、全てのsubmoduleでmasterブランチを追ってる場合は

$ git submodule update --init
$ git submodule foreach git checkout master

な感じでupdateした後に全てを現在のmaster状態にしてあげれば良いかと思います。 (取ってきたところでも積極的に更新、開発したい場合。)

submoduleで特定のブランチを使いたい場合にも、submoduleの中で

$ cd submodules/evernote_mail
$ git checkout test_branch

等としてsubmoduleの状態を変更して親レポジトリでadd&commitすれば このブランチがupdateした際に取ってこられるようになります。

submoduleの削除

$ submodule_dir=submodules/evernote_mail
$ git submodule deinit ${submodule_dir}
$ # git config --file .gitmodules --remove-section submodule.${submodule_dir} # not needed for the latest git?
$ git rm ${submodule_dir}

最初のdeinitでevernote_mailディレクトリ内を全て消して .git/configからも情報を消します。

追記: 2014/06/29

gitのバージョンをあげたせいか、上の方法で行うと 最後のrmの所で

$ git rm $d
fatal: Please, stage your changes to .gitmodules or stash them to proceed

と言われてしまうようになりました。

$ git commit -m "removed evernote_mail"
$ git rm $d
rm 'submodules/evernote_mail'
warning: Could not find section in .gitmodules where path=submodules/evernote_mail
(-_-) $

と一度.gitmoduleへの変更をcommitしてから消すとWarningは出ますが消せます。 一応.git/config.gitmoduleからはきちんと削除されてるし これで問題無いとは思いますが。

ただhelpとかを読み返して試して見た感じ、上の様にgit configで消さなくても良くて、 deinitした地点で.git/configから情報削除(現在のwork treeから削除、中身も消す)、 git rmをすることでディレクトリを削除し.gitmoduleも掃除して情報を消す、 作業を行ってくれる様です。

なので、git configの部分はスキップして(したほうが?)良いみたいです。

追記ここまで

次に、gitmodulesからも消してsubmoduleの登録を消します。

最後に空のディレクトリを消して終わりです。

もし、親レポジトリからは外したいがファイルは残しておきたい、と言う場合には

$ submodule_dir=submodules/evernote_mail
$ git config --remove-section submodule.$submodule_dir}
$ git config --file .gitmodules --remove-section submodule.${submodule_dir}
$ git rm --cached ${submodule_dir}

のように、最初にsubmodule deinitを使う変わりgit config.git/configから削除だけして、 最後に--cachedオプションを付けることでファイルやディレクトリは残したまま 親レポジトリへの登録だけ外します。

追記: 2013/11/07

ごちゃごちゃと消す作業をしてるとゴミが残ってしまって、 改めてsubmoduleに加えようとする時

$ git submodule add  [email protected]:rcmdnk/evernote_mail.git ./submodules/evernote_mail
A git directory for 'submodules/evernote_mail' is found locally with remote(s):
  origin        [email protected]:rcmdnk/evernote_mail.git
If you want to reuse this local git directory instead of cloning again from
  [email protected]:rcmdnk/evernote_mail.git
use the '--force' option. If the local git directory is not the correct repo
or you are unsure what this means choose another name with the '--name' option.

等と出ることがあります。

How do I remove a Git submodule?

これなんかを見るにやはりsubmoduleを消すのは一筋縄で行かない所もあるのかな、 と言った印象も受けます 1

で、deinitをしたり.gitmodules.git/config等から消したりしても 上手く行かなかったんですが、 結局、問題は.git/modules/submodules/evernote_mailと言うディレクトリが残ってて、 これが新たにsubmoduleとし追加することを拒んでた模様。

このディレクトリを直接削除してsubmodule addしたら上手く行きました。

追記ここまで

ignore = dirty

追記: 2013/11/07

submodule内で何かファイルの変更があり、 それをsubmodule内ではcommitしたりする必要がない場合 (何らかの設定ファイルなどをレポジトリ自体に作ってしまったりするようなものの場合)、 親ディレクトリから見ると

$ git diff
diff --git a/submodules/evernote_mail b/submodules/evernote_mail
--- a/submodules/evernote_mail
+++ b/submodules/evernote_mail
@@ -1 +1 @@
-Subproject commit 601bbca665de752c956a23b61e3e54dd16d62764
+Subproject commit 601bbca665de752c956a23b61e3e54dd16d62764-dirty

こんなかんじのcommitにdirtyが付いたものなってしまいます。 この変更は親ディレクトリからgit add -Aなどしても 駄目なので、この変更をsubmodule自体に反映させたい時は submodule内でaddcommit、等してから 親ディレクトリでgit add -Aなりしてこの新しい状態を 登録し直してあげます。

ただ、変更が(何らかの設定ファイルのような)ローカルだけに必要な物の場合や 自分の管理でないものだったりする場合、

$ git config --file .gitmodules submodule.submodules/evernote_mail.ignore dirty

の様にして、.gitmodulesignore = dirtyを加えます(直接書いても勿論OK)。

[submodule "submodules/evernote_mail"]
	path = submodules/evernote_mail
	url = [email protected]:rcmdnk/evernote_mail.git
	ignore = dirty

これでこのsubmodule内で何らかのコミットされてない変更があっても 親ディレクトリからは無視するようになります。

追記ここまで

追記: 2013/12/20

submoduleを一気に消したりするエイリアスを作ってみました。

Gitのエイリアスで引数を使う

追記ここまで

Submoduleのプロトコルの変更

Sponsored Links
  1. そのうちgit submodule rmみたいなコマンドが出来ないかな。。。 ただ、そういうwrapperコマンドみたいのが増えてきて実際やってること分からなく なると困る、っていう思想もありそうな。

    git pullのこの話の様な。

    Git pullを使うべきでない3つの理由

Sponsored Links

« footnote-extra: Octopress用footnoteのプラグイン Mavericksへアップデート »

}