git subtree の使い方メモ

仕事で使う技術的なこと

git subtree のやり方をいつも忘れてググるのを繰り返しているので、備忘録で残します。

似たような概念で git submodule というのがありますが、ここではその違い等には言及しません。

git subtree によるサブリポジトリの切り出し方

ユースケース

  • リポジトリAを作成しており、内部に hoge ディレクトリがある
  • ある日、別のリポジトリBでも、A内のhogeを流用したくなった
  • hoge をライブラリ化して、リポジトリBに取り込む

やることのイメージ

  1. hoge を別リポジトリに切り出す

  1. B内に、hoge を取り込む

手順

git subtree split でライブラリを切り出す

リポジトリAにて、以下のコマンドを実行します。
ここでは、hogeを切り出す際に使用するブランチ名を、適当に hoge_work としてます。

git subtree split --prefix=hoge --rejoin -b hoge_work

上記を実行すると、hoge ディレクトリのみに切り出された hoge_work ブランチが作成されます。
ここで、--rejoin はいったんおまじない的なものととらえておきましょう。
--rejoin することで、後述する更新時に再度split実行する際の速度が速くなるらしいです。こちらに解説があります)

subtree split はそこそこ時間がかかります。かなり大きめのブランチだと、私の環境では1時間弱かかりました・・・

切り出したライブラリをリモートリポジトリに push する

あらかじめ、リモート側(例えば github とか)に、ベア(空の状態)で hoge リポジトリを作っておきます。

そして、そのリモートのパスを、リポジトリA内に登録します。
tortoise git の場合は以下のような感じになります。

以下の通り、push します。

git push hoge hoge_work
切り出したライブラリを取り込む

切り出した hoge リポジトリを、リポジトリBに取り込みます。
ここからは、リポジトリBで操作します。

取り込む方法としては、subtree で取り込む方法と submodule で取り込む方法があると思いますが、今回は submodule で取り込む方法を記載します。
(ちなみに、経験上、リポジトリAとリポジトリBの両方でsubtree扱いにするとカオスになるのでお勧めしません・・・)

私は普段 tortoise git を使っているので、リポジトリBにて以下のように操作します。

以上で、リポジトリB内にリポジトリAの hoge
ディレクトリを取り込み完了です。

git subtree で運用している際の更新方法

ユースケース

2パターンのユースケースが想定されます。

  1. リポジトリAで更新した内容を、ライブラリhogeのリポジトリBに反映させる
    • リポジトリAをある程度バージョンアップしていった場合、hogeディレクトリも更新されていることが想定される
    • リポジトリA内の hoge を再切り出しして、リポジトリBに反映させる
  2. ライブラリhogeで更新した内容を、リポジトリAに反映させる

手順

私が知りうる限り、ユースケース2の方が断然処理が早いです。というか、ユースケース1のやり方はかなり時間がかかります。
ただし、実際の運用ではユースケース1のパターンの方が多いので、subtree による運用はそこそこ時間的コストがかかるとういことを理解しておく必要があるのかなと思います。(私がちゃんと理解できていないかもですが・・・)

ユースケース1の場合

リポジトリからライブラリを再度切り出す

以下のコマンドを実行し、リポジトリAからライブラリ hoge の再切り出しを行います。

git subtree split --prefix=hoge --rejoin --onto hoge_work -b new_hoge_work

前述の --rejoin により、前回切り出したときの記録がコミット履歴に残っていて、それを目印に、不要な切り出し処理がスキップされるらしいです。しかし、私が実行してみた限り、処理速度は体感あまり変わらない感じでした。

また、--onto を付けることにより、前回切り出したときのブランチ(hoge_work)から派生された履歴が作られます。
(それの何が良いかというと、リモートに切り出し済みのhogeリポジトリと、ブランチの互換性があるのでpushしやすいということです)

切り出したコミットログから、ブランチを更新する

ここがハマりポイントになるのですが、何故か上記の切り出しのままでは、hoge_work ブランチは更新されていません。
切り出しにより作成されるのは、あくまで hoge_work から派生されたコミットログだけみたいです。

上述の切り出しが成功した際には、コミットログは下記のようになっているはずです。
ここで、Split ~~ into commit ~ となっているコミットが hoge_work から派生されたものになります。

なので、このコミットログを明示的に hoge_work にマージしてやる必要があります。

切り出したライブラリをリモートリポジトリに push する

初回の切り出し字と同じ手順となりますので、詳細は省略します。

git push hoge hoge_work
切り出したライブラリを取り込む(更新する)

更新した hoge リポジトリを、リポジトリBに反映させます。
ここからは、リポジトリBで操作します。

後述した通り、ここでもリポジトリBでは submodule による運用を想定します。

リポジトリB内でサブモジュールとなっている hoge ディレクトリ内で pull を実行すると更新されます。

ユースケース2の場合

ライブラリのリポジトリを取り込む

あらかじめ、リモートのhogeリポジトリが更新済みであるとします。

ここでは、リポジトリAへsubtreeとして更新する場合を説明します。(リポジトリBへsubmoduleとして更新する方法はユースケース1と重複するので省略します)

リポジトリAにて、以下のコマンドを実行します。

git subtree merge --prefix=hoge --squash hoge hoge_work

--squash はなくてもいいですが、ないとコミット履歴が思わぬ大きさで膨らむことがあるので、とりあえずつけておくこと推奨です。

なお、--squash の後の hoge はリモートの名称、そのあとの hoge_work はリモート上のブランチ名です。

この方法の場合、これで更新完了で、時間もほとんどかかりません。

コメント

タイトルとURLをコピーしました