subtree コマンドで別のリポジトリを指定したフォルダに取り込んでpushとかpullをできるようにする

# 前提
parent リポジトリと child リポジトリがある

# 目標
1. parent リポジトリの　/parent_child/ フォルダに child リポジトリをsubtreeとして取り込む
2. child リポジトリの更新を /parent_child/に反映する (subtree pull)
3. parent_child フォルダの更新を child リポジトリに反映する (subtree push)

<pre>
    こんなかんじ
    parent--parenttxt (ここはparent repo)
          |-parent_child--child.txt (ここのフォルダに child repoを反映)
</pre>

# 手順
## 1. child リポジトリ の取り込み
+ parent リポジトリと child リポジトリを作成する(すでにあれば飛ばす)

```sh
mkdir parent && pushd parent && git init && popd
mkdir child && pushd child && git init && popd
pushd parent && touch parent.txt && git add . && git commit -m "parent 1st commit" && popd
pushd child && touch child.txt && git add . && git commit -m "child 1st commit" && popd
```

+ subtreeとして取り込む

```sh
cd parent
# child リポジトリを remoteとして登録 -f で fetch も行なっておく
git remote add -f child_remote ../child/
# subtreeとして登録 --squashで一つのコミットとして取り込む
# git subtree add --prefix=`subtreeのフォルダ名` `subtreeに取り込むリポジトリ` `取り込むブランチ` --squash
git subtree add --prefix=parent_child child_remote master --squash
# parent_child フォルダ内がchild リポジトリの構成になっていることを確認
ls parent_child/
# 取り込まれたコミットを確認
git log HEAD^2 -1 -p
```

## 2. child リポジトリの更新の反映
+ child リポジトリの更新

```sh
cd ../child/
# child リポジトリの更新
echo "update text after subtree-add" > child.txt 
git commit -am "update child #1"
cd ../parent/
```

+ child リポジトリを parent リポジトリの parent_child フォルダに反映

```sh
# child リポジトリの反映
# git subtree pull --prefix `subtreeのフォルダ名` child_remote master --squash
# `subtreeのフォルダ名` の最後が / だとうまく動かないので注意
git subtree pull --prefix parent_child child_remote master --squash
# 反映されたコミットを確認
git log HEAD^2 -1 -p
```

+ childリポジトリの複数のコミットがひとつのコミットとしてparentリポジトリに反映されることを確認

```sh
# childリポジトリに二つのコミットを作成
cd ../child/
echo "update text after subtree-add #2" for 2 commit pull >> child.txt 
git commit -am "update child #2"
echo "update text after subtree-add #3" for 2 commit pull >> child.txt 
git commit -am "update child #3"
cd ../parent/

# child リポジトリの反映
git subtree pull --prefix parent_child child_remote master --squash

# ひとつのコミットとして反映されていることを確認
git log HEAD^2 -1 -p
```

## 3. parent リポジトリの parent_child内の変更を childリポジトリへ反映

+ bareではないリポジトリへのpushでエラーが出ないように下記を設定

```sh
# このサンプルでは bare リポジトリを使っていないため下記コマンドを実行する
# bare に対してpushする場合は不要
cd ../child/
git config receive.denyCurrentBranch ignore
cd ../parent/
```
+ parent_child内にファイルを作成するコミットを2回実行

```sh
cd parent_child/
touch parent_add1.txt && git add . && git commit -m "add from parent #1"
touch parent_add2.txt && git add . && git commit -m "add from parent #2"
cd ..
```

+ child リポジトリへ subtree push

```sh
git subtree push --prefix parent_child child_remote master

# 反映されていることを確認
cd ../child/ && git log -3 -p
```

+ subtree外のファイルがpush で反映されないことを確認

```sh
# subtree内にファイルを作成しcommit
cd ../parent/parent_child/
touch parent_add3.txt && git add . && git commit -m "add from parent #3"

# subtree外にファイルを作成しcommit
cd ..
touch parent_add4.txt && git add . && git commit -m "add from parent #4"

# subtree内とsubtree外にファイルを作成しcommit
# 内
cd parent_child && touch parent_add5.txt 
# 外
cd .. && touch parent_add6.txt 
git add . && git commit -m "add from parent #5 & #6"

# child リポジトリへ subtree push
git subtree push --prefix parent_child child_remote master

# subutree内の変更のみ反映されていることを確認
# parent_add4.txtの追加コミットが反映されないことを確認　および
# parent_add5.txt,parent_add6.txt を追加したコミットに、
# parent_add6.txt がないことを確認
cd ../child/ && git log -3 -p
```

# まとめ
+ subtreeの取り込みは git subtree add 
+ subtreeとして取り込んだリポジトリの変更は `subtree pull` で簡単に取り込める
+ subtreeとして取り込んだリポジトリへの変更は `subtree push` で簡単に反映させることができる
    + 注意点としてsubtree外のファイルを同時に変更したコミットがあった場合、subtree外のファイルはcommitから削除された状態でpushされる
+ 挙動が理解できたら subtree コマンド便利そう



#### 参考
[Alternatives To Git Submodule: Git Subtree](http://blogs.atlassian.com/2013/05/alternatives-to-git-submodule-git-subtree/)