Git: Submodule地獄からの脱出

git の submodule、便利ですが安定してない(変更の多い)リポジトリに使うと **地獄です。**
Submodule Hellです。
やばいです。

開発で変更が入りまくるリポジトリをうっかりsubmoduleにしちゃった！
かなり開発して入り組んできたけどどうしよう…。



#なぜsubmoduleが地獄になるのか？
* 更新が多いリポジトリはコミットが2箇所になって地獄
* しかも変更がsha-1形式でしか分からなくて地獄
* submoduleのクラス名・メソッド名等をリネームすると、親側もリネームが必要になって無駄に2回のコミットをしなくちゃいけなくて地獄。
    * さらに、親のコミットはコードの修正＆submoduleの変更の取り込みも必要
* submoduleの参照先同士が衝突した時、何が悪いのか探すのが大変すぎて地獄
* いともたやすくsubmodule側が detached HEAD になって自分のコミットを見失うので地獄(reflogから復活とかある)
* PullRequestの時に、何個も送らなきゃ行けなくて地獄
* PullRequestをレビューする方も、見る箇所が別ページに行くの必須で地獄
* ブランチの数も x submodule数で増加するので管理コスト増加で地獄
* この悩みわかってない人に、「え、だって機能毎に分割してるんでしょ？いいじゃん」とか言われたりして地獄

####サブモジュールが素晴らしく見える理由
* 分割統治
* 複数の箇所で利用可能
* 意味的に別リポジトリがスッキリする

まぁ、基本的に完全に安定しているライブラリをリンクして使う…みたいな使用法以外では使わないほうが良いと思います。

できれば月1回以下の更新が望ましい…。週1は辛い(フレームワークのBeta版とかだとよくある）

**毎日submoduleを更新しなきゃいけない状況をsubmodule地獄と呼びます**。(命名：俺。["Submodule Hell"で検索](https://www.google.co.jp/webhp?#q=submodule-hell)すると結構ありますが。）

基本的に **submodule地獄になるような状況を最初から回避する** ことが一番大切です。

## いまsubmodule地獄にいるんですけど！？
地獄に入ってしまったらしょうがない。
脱出しましょう。

# 脱出法１：submoduleを親リポジトリに統合する方法
ようするに、親リポジトリとsubmoduleリポジトリに分かれているから良くない。
ひとつに統合しましょう。（[参考](http://japan.blogs.atlassian.com/2013/07/git-submodules-workflows-tips/#tip3) ）

## submoduleを親リポジトリに統合する
```shell-session
git rm --cached testSubModule
```

```shell-session
git rm .gitmodules 
```

```shell-session
rm -rf testSubModule/.git 
```

```shell-session
git add testSubModule

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is up-to-date with 'origin/master'.
#
# Changes to be committed:
#       deleted:    .gitmodules
#       deleted:    testSubModule
#       new file:   testSubModule/README.md
#       new file:   testSubModule/...submoduleのファイルがたくさん...
#       new file:   testSubModule/...
#       new file:   testSubModule/...
#       new file:   testSubModule/...
```

##もう一度submoduleに分割する方法
安定版になった！submoduleに分割したい！という時には以下の方法で。

親リポジトリから削除します。

```shell-session
git rm -r --cached testSubModule
```

新しいリポジトリとして構築し直します。

```shell-session
cd testSubModule
git init
git add .
git commit -m "change to submodule"
```
これで新しいリポジトリを作成しました。
さらにremoteとして、元々使ってたsubmoduleのパスを追加します。

```shell-session
git remote add origin リモートリポジトリのパス(git@github.com:Xxx/testSubModule.git等)
```


pullし、おそらく衝突するので、強制的にローカルの内容で上書きし、コミットします。

```shell-session
git pull origin master
git checkout --ours .
git commit -a
```

最期にpushしてやれば終了です。

```shell-session
git push origin master
```


submoduleとして追加し直す場合は、 `git rm testSubModule ` の後に親の階層に戻り、 `git submodule add リモートリポジトリのパス` とかすれば良いと思います。
　

---

さて、この方法ですが察しの良い方はお気づきと思いますが、この方法だと、当然ですが統合・再分割の過程において **履歴は消えます** 。

「履歴もsubmoduleのほうに持って行きたい！＞＜」とお考えの方には、残念ですが、現状だとそういう方法は <del>無いです…</del> …あるけど、危ういです。

#脱出法２：履歴を持ったまま統合・そして分割する
`git --filter-branch`という最強のオプションを使います。
**歴史を改ざんします**。よく分かってないと怖いですね。

## 履歴を持ったまま、親リポジトリに統合する方法

[こちら](http://qiita.com/edvakf@github/items/9e7ccbaa944d26f9b69c) を参考にさせていただきました。

```shell-session
git filter-branch --index-filter \
  'git ls-files -s | sed "s@[[:cntrl:]]\"*@&testSubModule/@" |
      GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
        git update-index --index-info &&
  mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || true' HEAD
```
mac以外の環境では`[[:cntrl:]]`部は、単に `/t` に置き換えてください。



作業ブランチを作成。

```shell-session
git checkout -b __work_for_import__
```

親ブランチに移動し、取り込む。
作業パス(cdの移動先）は環境に合わせて適切に。

```shell-session
cd ..
git remote add importSubmodule testSubModule/
```


リモート(いままでsubmoduleだったところ)から取得。
履歴を含んで testSubModule/ ディレクトリ以下に、submoduleのファイルが移ってきました。

```shell-session
git pull importSubmodule __work_for_import__
```

##履歴を持ったまま、submoduleに分割する方法

上記の逆です。


`git filter-branch` を使って、 `testSubModule/`ディレクトリ以外の変更を「なかったこと」にします。

```shell-session
git filter-branch --subdirectory-filter testSubModule/ HEAD
```


submoduleだったリポジトリにPUSHして終了。
（ただ、こういう時は新しくリポジトリを作ったほうが絶対安全です）

```shell-session
git push importSubmodule __work_for_import__
```

親リポジトリがすべてなかったことになってしまったので、修復をかけます。

```
git reset --hard HEAD^
```

あとは通常にsubmoduleを取り込んでください。
また、この処理はリポジトリを汚しまくる＆処理時間がかなり掛かると思うので、注意して行ってください。


#まとめ

切出したり戻したりするの大変だから、安定版まで親リポジトリと一緒に育てて
**submoduleが安定した時にディレクトリごと切り出せ**

地獄から脱出する前に、地獄に来ないことをオススメします。

　
　

おわり

　
---


あ、あと `git subtree`はこの問題についてある程度の解決策になります。
ただし、更新頻度が高くsubmodule間の依存性が高いような場合は、やはり親リポジトリに統合した方がよいでしょう。