git-subtree移行メモ

ProjectAの一部として開発していたライブラリをProjectBでも使いたい！みたいなことがあったので、
git-subtreeを使う練習。

# git-subtreeの始め方

## 既存のライブラリを取り込む

ProjectAにAwesomeLibraryをgit-subtreeを使って取り込む例。
サンプルで作ってみたレポジトリに構成はこうなってます。

```
$ cd ~/AwesomeLibrary
$ git log --graph --oneline
* 5e355de すごい機能追加
$ cd ~/ProjectA
$ git log --graph --oneline
* 5484d65 README追加
```

git-subtreeを使ってProjectAにAwesomeLibraryを取り込んでみます。

```
$ cd ~/ProjectA
$ git subtree add --prefix=lib ~/AwesomeLibrary master
git fetch ../AwesomeLibrary master
warning: no common commits
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From ../AwesomeLibrary
 * branch            master     -> FETCH_HEAD
Added dir 'lib'
```

ログを見てみると、AwesomeLibraryのログがきちんと引き継がれてますね。

```
$ git log --graph --oneline
*   b55682f Add 'lib/' from commit '5e355de41bd66b2add9acab0d51761f744efb723'
|\  
| * 5e355de すごい機能追加
* 5484d65 README追加
```

```
$ git subtree split --prefix=lib -b cool-feature
-n 1/       2 (0)
-n 2/       2 (1)
Created branch 'cool-feature'
c1b8c8812d15dc4a663cdecb0edb563461b5afbe
```

## プロジェクトの一部をライブラリとして切り出す

今度は逆にプロジェクトからライブラリを切り出してみます。

```
$ cd ~/ProjectB
$ git log --graph --oneline
* bc9e4a4 カッコイイ機能追加
* 5329249 README追加
```

libディレクトリ以下の変更だけcool-featureブランチに分離する。

```
$ git subtree split --prefix=lib -b cool-feature
-n 1/       2 (0)
-n 2/       2 (1)
Created branch 'cool-feature'
c1b8c8812d15dc4a663cdecb0edb563461b5afbe
```

lib以下の変更だけを含んだブランチができるので、あとはgit pushとかで別のレポジトリに入れればOK。

```
$ git checkout cool-feature 
Switched to branch 'cool-feature'
$ git log --graph --oneline
* c1b8c88 カッコイイ機能追加
```

# 変更を加える

## ライブラリの変更をプロジェクトに取りこむ

ライブラリに新しい機能が追加されました。

```
$ cd ~/AwesomeLibrary
$ git log --graph --oneline
* 3037dc4 新しい機能追加
* 5e355de すごい機能追加
```

git subtree pull を使うと追加された機能を取り込める。

```
$ git subtree pull --prefix=lib ../AwesomeLibrary
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From ../AwesomeLibrary
 * branch            HEAD       -> FETCH_HEAD
Merge made by the 'recursive' strategy.
 lib/BrandNewFeature.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 lib/BrandNewFeature.txt
```

```
$ git log --graph --oneline
*   221fd21 Merge commit '3037dc4f8dd1a2f912e62291bfadfc022cd7a268'
|\  
| * 3037dc4 新しい機能追加
* |   b55682f Add 'lib/' from commit '5e355de41bd66b2add9acab0d51761f744efb723'
|\ \  
| |/  
| * 5e355de すごい機能追加
* 5484d65 README追加
```

## プロジェクトに加えた変更をライブラリにも反映する

実は「プロジェクトの一部をライブラリとして切り出す」と一緒。

```
git subtree push --prefix=lib origin new-feature
```

とすれば、git subtree split と git push を一緒にやれる。

# git-subtreeを使ったときに追加されるコミットメッセージについて

git-subtree を使うとコミットメッセージに、どのディレクトリをいじったかや、マージしたコミットはどれかという情報が追加される。
この情報をもとにgit subtree split で処理するコミットの範囲を絞り込んでいるので、処理の高速化に非常に重要な存在。
このコミットメッセージがないとレポジトリの全コミットを舐めるので、コミット数によってはかなり重い。
どんなときにつくのか整理。

## addしたとき

git subtree add したときにコミットメッセージが設定される。
git subtree split を実行したときに、このコミットより古いコミットは見なくなるので、処理が早くなるはず。

```
$ cd ~/ProjectA
$ git subtree add --prefix=lib ~/AwesomeLibrary master
$ git show -s HEAD
commit b55682fd03a4af4b5025b6c7e257713cb45cfd89
Merge: 5484d65 5e355de
Author: Ichinose Shogo <shogo82148@gmail.com>
Date:   Mon Nov 10 01:42:39 2014 +0900

    Add 'lib/' from commit '5e355de41bd66b2add9acab0d51761f744efb723'
    
    git-subtree-dir: lib
    git-subtree-mainline: 5484d65a4b1f12576975302ee5905de32622b5aa
    git-subtree-split: 5e355de41bd66b2add9acab0d51761f744efb723
```

## rejoinしたとき

git subtree split コマンドを使うときにrejoinオプションをつけると、新しく作ったブランチと元のブランチをマージしてくれる。
このときにコミットメッセージを設定してくれる。

```
$ git subtree split --prefix lib --rejoin
-n 1/       2 (0)
-n 2/       2 (1)
Merge made by the 'ours' strategy.
c1b8c8812d15dc4a663cdecb0edb563461b5afbe
$ git log --graph --oneline
*   b3fd19a Split 'lib/' into commit 'c1b8c8812d15dc4a663cdecb0edb563461b5afbe'
|\  
| * c1b8c88 カッコイイ機能追加
* bc9e4a4 カッコイイ機能追加
* 5329249 README追加
```

git subtree split を実行したときに、このコミットより古いコミットは見なくなるので、これも処理速度アップに効果があるはず。


## squashしたとき

git subtree pull や git subtree merge のときに --squash オプションをつけると、実際にマージをする前にコミットを一つにまとめてくれる。

```
$ git subtree pull --prefix=lib --squash ../AwesomeLibrary
From ../AwesomeLibrary
 * branch            HEAD       -> FETCH_HEAD
Merge made by the 'recursive' strategy.
 lib/BrandNewFeature.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 lib/BrandNewFeature.txt
```

```
$ git log --graph --oneline
*   0d5df88 Merge commit 'ef35a52414ce22672c2ad987450b4c24534a31f1'
|\  
| * ef35a52 Squashed 'lib/' changes from 5e355de..3037dc4
* |   b55682f Add 'lib/' from commit '5e355de41bd66b2add9acab0d51761f744efb723'
|\ \  
| |/  
| * 5e355de すごい機能追加
* 5484d65 README追加
```

git subtreeは、Mergeコミットのコミットメッセージには触れず、Squashedとなっているコミットにだけ「git-subtree-dir:」をつけるみたい。
この例では変更を加えなかったけど、b55682f..0d5df88の間でlibディレクトリが変更されている可能性があるからかな。
プロジェクトレポジトリに大量にコミットがある場合なんかはgit subtree splitの速度向上には役に立たない。


# まとめ

- git submodule add で取り込んだディレクトリに対してはなるべく変更をしない方がよさそう
    - git subtree split が重い(git subtree pushも中でsplitしているのでやっぱり重い)
    - 範囲を上手く絞れないと全コミットを舐めることになる
- rejoinを上手く使う
    - splitのときにrejoinを使っておくと、次にsplitするときに範囲を絞り込んでくれるので早くなる
    - add や rejoin 以外の操作ではあんまり絞り込みの効果は期待できない


# 参考

- [git subtree使ってみた](http://qiita.com/taizawa/items/64dddf308150c1317705)
- [subtree.txt](https://github.com/git/git/blob/master/contrib/subtree/git-subtree.txt)
- [subtree.sh](https://github.com/git/git/blob/master/contrib/subtree/git-subtree.sh)