Edited at

git-subtree移行メモ

More than 1 year has passed since last update.

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 co 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 以外の操作ではあんまり絞り込みの効果は期待できない




参考