2014年9月8日月曜日

Git subtreeによるライブラリ管理について

前回は Git subtree merge について説明しましたが、今回はそれに深い関係がある Git subtree コマンドについての説明です。

まず基本的なこととして、 Git subtree は Git subtree merge と同じものではありません。 subtree merge はマージ戦略として subtree を指定したマージにすぎませんが、 Git subtree は明確に外部ライブラリの取り込みと submodule の代替を目的として設計された機能です。 Git 1.7.11 以降であれば、 Git subtree を使用するのが望ましいでしょう。

なぜなら、 Git subtree には、 Git subtree merge にはない、下記のような機能があるからです。

  • 取り込んだライブラリ側の歴史を"潰し"(squash)てコンパクトな歴史にできる。
  • 自分のプロジェクトでライブラリ側に変更を加えた場合、それをライブラリ側(上流)のリポジトリにプッシュしたり、プルリクエストしたりできる。
  • ライブラリの取り込みの初期化のためのコマンドが用意されており、3コマンドぐらい要するところを1コマンドで直観的に実施できる。
あとは、比較的小さな利点ですが、下記の特徴があります。

  • Squash したライブラリの歴史には、途中経過のすべては保存していないので、無駄なディスク容量を食わない。(ハッシュだけ記録している)
  • Squash の有無に関わらず、 Git subtree を実施したリポジトリは、 Git subtree に対応していないバージョンの Git でも扱える。たとえば Git subtree 担当者以外は、中央リポジトリや、他のプロジェクトメンバーの Git のバージョンが古くても運用できる。(ただ、 Git subtree に関係するコマンドが打てないだけ)


Git subtree の使い方については、 Atlassian Blog の下記エントリがもっともよくまとまっています。

しかし、原文が英語の日本語翻訳版なので、あらためて噛み砕いて違った角度から説明してみようと思います。





例として、下記 app プロジェクトを開発しているとき、 lib ライブラリをソースに取り込みたいという状況を考えます。


ここで、 app リポジトリにおいて、下記コマンドを打つことで、 app リポジトリの lib サブディレクトリに lib リポジトリのソースが取り込まれます。

git subtree add --prefix=lib <lib-repositoy> master

<lib-repository> は lib リポジトリを指すパスか、URLで置き換えてください。

これは、 Git subtree merge について説明した、ライブラリの最初の取り込みとほぼ同じ結果です。

上記コマンドに --squash フラグを付けることで、ライブラリ側の歴史を一つのコミットにまとめることができます。

git subtree add --prefix=lib <lib-repositoy> master --squash




app のコミットよりも、 lib のコミットのほうが活発に行われているような場合は、混乱を防ぐため squash するとよいでしょう。

さて、 squash はとりあえずしなかったという仮定で、時間が進み、 app と lib のそれぞれについて歴史が進んだとします。



ここで、 app リポジトリにおいて、 lib リポジトリの更新内容を取り込みたいと思った場合は、次のコマンドを打ちます。

git subtree pull --prefix=lib <lib-repository> master




さて、ここで app リポジトリにおいて lib サブディレクトリに対して変更を加えたとします(ここでは、lib3.txtを追加してみました)。



この変更を lib にプッシュしたい場合は、次のようにします。 lib のリポジトリに書き込み権限がある必要があります。

git subtree push --prefix=lib <lib-repository> app-lib

lib リポジトリ側で区別できるようにするため、 app-lib という名前のブランチにプッシュしました。


これ以後、 git subtree pull したときに、ライブラリ側に反映した変更は競合にはなりません(cherry-pick をマージしたときと同じ考え方)。

オープンソース系の外部ライブラリのように、自分が所有しているのではなく、書き込み権限がないリポジトリである場合、 lib へのプッシュは直接にはできませんので、ライブラリを管理している人なり組織に、プルリクエストやパッチを投げることになります。このために、ライブラリの clone を自分の環境に作り、そこへプッシュする必要があるかもしれません。

いずれにせよ、 Git subtree はリモートのURLをメタデータとして記憶しないので、リモートのURLが変わったり、ローカルの clone に切り替えたりといった構成変更が柔軟にできます(同じリポジトリのデータであることは、コンテンツベースのハッシュが保証します)。これは、 Git submodule に対して有利な点です。

なお、 <lib-repository> で示したリモートリポジトリは、パスやURLではなく、登録したリモートでも使えます。


私個人では、自作のちょっとしたライブラリをいろいろなプロジェクトで共有するために、 Git subtree を使い始めています。今までのバージョン管理手法だと、規模の小さなライブラリを独立させて管理するのは、管理の煩わしさがオーバーヘッドとなって気が進みませんでしたが、 Git subtree であれば、「後からでも何とでもなる」という気持ちで始められると感じています。

ライブラリ化するのが気軽になったことで、最初からライブラリにできるような再利用性の高いコーディングをしようと思うようになったところは、コーディングスタイル上にも良い影響があったと思います。

0 件のコメント:

コメントを投稿