Edited at

Git Subtree 事始め

More than 1 year has passed since last update.

もうそろそろいい加減git subtreeも使いこなせないとまずいな、と思っていたところなので

とりあえずわかる範囲で調べてまとめてみた。


git subtree とは何か

git subtreeは外部のリポジトリを現在のリポジトリに取り込むための機能です。

リボジトリ in リポジトリとなるとgit submoduleだったり、

npmとかcomposerみたいな依存管理ライブラリみたいなものを連想するかも知れませんが、

subtreeの挙動はちょっと違います。

一般的な依存管理ライブラリや、git submodule では、

特殊なファイルに 取り込み元リポジトリを記載して管理するという、

取り込み元と、取り込み先を明確に区別しての管理を行うのですが、

subtree では、

「取り込み元と取り込み先のコミット履歴を完全に結合する」

という動き方をするみたいです。


使い方

とりあえずフロントエンド開発でよく使うライブラリを使って、

いろんなgit管理プロジェクト内で利用したり更新したりしたいなぁという所が始まりです。

とりあえず練習用に、

https://github.com/chatbox-inc/frontend.git

という形でgulpのスクリプトなどを雑にまとめたリポジトリを用意しました。


事前準備

とりあえず事前準備として、取り込むライブラリのリポジトリをremoteに登録しておきます。

$ git remote add frontlib https://github.com/chatbox-inc/frontend.git


プロジェクトにライブラリを追加する。

とりあえず空のプロジェクトを作ってみます。

$ mkdir hogeProject

$ cd hogeProject
$ git init
$ git commit -m project_first_commit --allow-empty

frontend ディレクトリを作成してディレクトリにライブラリをぶち込みます。

$ git subtree add --prefix=frontend frontlib master

frontendディレクトリが作成されて、綺麗にライブラリが打ち込まれました。


ここで何が起こっているのか

何が起こっているのかを見るには、gitのログを確認してみるのが一番です。

$ git log --oneline

4cb013b Add 'frontend/' from commit '48f0fafda1cb73ef8abc72630ff2xse379958a1b'
0ec38xx project_first_commit
48ffewd frontlib_first_commit

自分で明示的に作成したコミットの他に、frontlibから取り込まれたコミットもコミットログに追加されている事がわかります。

2つのリポジトリのコミットログを混ぜて、そのマージコミットを注記する、のがsubtreeの構造っぽい。

別のディレクトリを切って、同じライブラリをぶち込んでみると、こんな感じ

$ git subtree add --prefix=frontend2 frontlib master

$ git log --oneline
ab96901 Add 'frontend2/' from commit '48f0fafda1cb73ef8abc72630ff2xse379958a1b'
4cb013b Add 'frontend/' from commit '48f0fafda1cb73ef8abc72630ff2xse379958a1b'
0e37c38 project_first_commit
48f0ffd frontlib_first_commit

48f0ffdが重複することは無く、単純にsubtreeのマージコミットのみが2つ記録されている。

マージコミットが、subtreeの状態を管理していて、取り込み元の48f0ffdはあくまでその参照として機能している、みたいな感じで、subtreeの挙動をふんわりと理解する事ができます。


サブツリーの削除

単純にサブツリーの削除を行う場合はrmしてcommitすればOKです。

$ rm -rf frontend2

$ git commit -m delete_frontend2

しかし、いくらrm->commit の手続きをとってもsubtree addを行ったという履歴は消えません。

誤って異なるリポジトリから大規模なコミットをsubtreeで取り込んでしまったとしても、

それはもうコミットであるため、容易に取り消す事はできません。

取消を行うためには、歴史の改変が必要です。

git filter-branch --tree-filter 'rm -rf frontend2' HEAD

filter-branchサブコマンドの--tree-filterオプションには、

プロジェクト内の各チェックアウトに対して指定したコマンドを実行して結果を再コミットする、という機能があります。


ライブラリの更新を取り込む/送信する

一旦subtreeの取り込みが終わったら、あとは楽ちん

$ git subtree push --prefix=frontend frontlib master

$ git subtree pull --prefix=frontend frontlib master

プロジェクトにおけるコミットも特に気にせず行ってOK。

frontendとそれ以外のディレクトリにまたがるコミットが存在しても自動的に切り分けて、

frontendのみのコミットを抽出してpushしてくれる。マジで賢い。


気になったこと

毎回コマンドでprefixとremote指定するのめんどくさい。

prefix と remoteのひも付けってどうやって管理するのがベターなんだろうか。


参考

http://qiita.com/shogo82148/items/04b29b195dbbb373152f