リポジトリ配下のサブディレクトリをサブモジュールに変更する

# 始めに

既存のリポジトリ配下のサブディレクトリを`Git submodule`で運用しようと思いましたら、思わぬところでつまずいてしまいました。
同じような人の助けになればと思い記事にしました。

Gitのバージョンは、
Git Shell git version 2.11.0.windows.3
(GitHub for Windwos 3.3.4.0)
になります。

# 要件・構成

既に存在しているリポジトリの配下のサブディレクトリを、サブモジュールで管理しなければいけなくなったとします。
変更前のリポジトリのディレクトリ構成は、以下のようなものです。

```text
Parent
│  parent.txt
│
└─Child         #これをサブモジュールにする
        child.txt
```

またブランチは、以下の3つがあるものとします。

-   master
-   develop1
-   develop2

サブモジュールの構成は、以下のように親モジュールの変更前と全く同じにします。
ここでは、単純化するためにChildには今までの変更履歴を残さずに新しいリポジトリを作ることにします。

```text
Child
    child.txt
```


# 下準備

まずは、既存のChildディレクトリをコピーして新しいリポジトリを作ります。

```text
cd Parent
cp -r ./Child ../Child

cd ../Child
git init
git add -A
git commit -m "first"
git remote add origin <ChildRemoteURL>
git push -u origin master
```

# 既存のブランチを削除できる場合

なかなかそういう事はないのでしょうが、master以外のブランチを全て削除できるとします。
その場合は、以下のように単純な操作で行えます。

```text
cd Parent
git checkout master
git branch -b develop1
git branch -b develop2
git rm -rf Child
git add -A
git commit -m "remove Child"
git submodule add <ChildRemoteURL>
git commit -m "add submodule"
git push --recurse-submodules=on-demand
```

ここでブランチを削除するコマンドの実行順序は重要ではありません。
プッシュの後に削除しても問題ありません。
開発作業を再開する前に、既存のブランチを削除し新しいブランチを作り直せば良いという事です。

これで今まで通りに運用することが出来ます。

# 既存のブランチを残す場合

まずは、ブランチを削除するのを除いて同じ操作をしてみます。

```text
cd Parent
git checkout master
git rm -rf Child
git add -A
git commit -m "remove Child"
git submodule add <ChildRemoteURL>
git commit -m "add submodule"
git push --recurse-submodules=on-demand
```

ここまでは、問題なく操作できます。
`git status`を見てみます

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

リポジトリはクリーンとなっています。

## ブランチの切り替え1

それでは、ブランチを切り替えてみます。

```text
git checkout develop
error: The following untracked working tree files would be overwritten by checkout:
       Child/child.txt
Please move or remove them before you switch branches.
Aborting
```

チェックアウトが中断されてしまいました。
そこで以下のコマンドを実行する必要があります。

```text
mv Child Temp         #git mv ではない
git checkout develop1
git merge master      #git rebase master
git checkout develop2
git merge master      #git rebase master
git checkout master
rm Child              #git rm ではない
mv Temp Child         #git mv ではない
```

詳しく解説します。
まずは、`git mv`を使わずにかわりにOS標準のmvを使って、ChildディレクトリをTempなどに名前を変更して退避します。
OS標準のmvを使うのは、Childを削除したことをステージングさせないためです。
mvの後の`git status`は以下のようなメッセージを返します。

```text
git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    Child

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        temp/

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

このまま、リポジトリがダーティになっている状態でも気にせずに更新する必要があるブランチにチェックアウトします。
そして、全てのブランチでマージ(リベース)してサブモジュールを導入した状態に更新します。
develop1、develop2にはtempが残りますがそのままにします。
全てのブランチを更新してmasterに戻ると`git status`は、以下のように変わっています。

```text
git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        temp/

nothing added to commit but untracked files present (use "git add" to track)
```

そして空のChildディレクトリが作られているのでそれをOS標準のrmで削除します。
最後に、退避して置いたTempをChildにOS標準のmvで名前を変更します。
ここで`git status`は、

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

となり、リポジトリがクリーンになっています。
この状態で再度develop1にチェックアウトすると、masterと同じのTempが消えたクリーンな状態になっています。

## ブランチの切り替え2

`git checkout -f`を使う場合は、以下のようなコマンドを実行します。

```text
git checkout -f develop1
git merge master      #git rebase master
git checkout -f develop2
git merge master      #git rebase master
git checkout master
git submodule foreach git checkout .
```

まずは更新したいブランチに`git checkout -f`でチェックアウトしマージ(リベース)します。
全てのブランチの更新が終わったらmasterにチェックアウトします。
そうすると、`git status`は以下のようになり、Childが空になっています。

```text
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:   Child (modified content)

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

Childの中身を元に戻すために、
`git submodule foreach git checkout .`
を実行します。
これでブランチの切り替えが正常に行えるようになります。

# 最後に

既存のリポジトリをサブモジュールで構成しなおすのは、珍しくなさそうな気がしますがすんなりいかないのに驚きました。
リポジトリがクリーンに見えてそうでなかったり、ダーティなはずがそうでなかったりとかなりモヤモヤします。
操作をかなり単純なブランチで行いました。
ブランチの状態に依っては上手く行かない事も出てくるのかもしれません。
実際の運用で問題が発生しなければ良いのですが･･･

# 参考

[Git-のさまざまなツール-サブモジュール#_サブモジュール使用時に気をつけるべきこと](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%84%E3%83%BC%E3%83%AB-%E3%82%B5%E3%83%96%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB#_サブモジュール使用時に気をつけるべきこと)