Git Submodule の代替:Git Subtree

Nicola Paolucci
Nicola Paolucci
リストに戻る

インターネットには、Git サブモジュールを使うべきではないという記事があふれています。私の評価はそれほど厳しくはないのですが、大筋で同意見です。若干の事例で submodules が役立つ場合があるものの、欠点はいくつもあります。

代替手段はあるでしょうか? もちろんあります。少なくとも 2 つのツールでは、Git を使い続けながら、プロジェクト内のソフトウェアの依存関係の履歴を追跡できます。

この記事では、git subtree に注目し、完全とまでは言えないもののそれが git submodule の問題を解決するものであることを説明してみます。

実際の例として、通常のユース ケースを実行してみましょう。ドットファイルで使用されている vim プラグインを簡単に保存し、最新の状態に保つにはどうすればよいでしょうか?

サブモジュールの代わりにサブツリーを使うのはなぜですか?

subtree の方が使いやすいと誰もが思う理由がいくつかあります。

  • シンプルなワークフローは管理が簡単です。
  • git の古いバージョンもサポートしています (v1.5.2 より古いバージョンにも対応しています)。
  • 親プロジェクトの clone が完了した直後からサブプロジェクトのコードが利用可能です。
  • subtree では、リポジトリのユーザーが新たに学ばなければならないことはありません。subtree を使用していることを関知せず、依存関係を管理することができます。
  • subtreesubmodules のように新しいメタデータ ファイル (.gitmodule) を追加しません。
  • 依存関係にあるモジュールの内容を変更する場合でも、そのリポジトリのコピーを別に持つ必要がない。

個人的には、このような欠点は許容範囲だと考えています。

  • 利用者には、新たなマージ戦略 (つまり、subtree) に関する知識が必要です。
  • サブプロジェクトの upstream にコードを戻す手順はやや複雑になります。
  • 親プロジェクト コードとサブプロジェクト コードをコミットに混在させない責任は各自が負います。

git subtree の使用方法

git subtree は、2012 年 5 月以降にリリースされた git (1.7.11 以降) で利用可能です。OSX 上で homebrew を使用してインストールしたバージョンには、subtree も適切に組み込まれていますが、プラットフォームによってはインストール手順に従った作業が必要です。

典型的な使用例として、git subtree を利用して vim プラグインのトラッキングを行う場合を説明しましょう。

リモート追跡を使用しない迅速かつ不正な方法

あるライナーをいくつかカット アンド ペーストしたいだけであれば、この段落をお読みください。

最初に、指定した prefix フォルダーに subtree を追加します。

 git subtree add --prefix .vim/bundle/tpope-vim-surroundhttps://bitbucket.org/vim-plugins-mirror/vim-surround.git main --squash

(一般的な方法では、サブプロジェクトの履歴のすべてを親リポジトリに保存することはありませんが、保存したい場合は --squash フラグを削除すればよいでしょう)

上記のコマンドで次の出力が生成されます。

 git fetch https://bitbucket.org/vim-plugins-mirror/vim-surround.git main warning: no common commits remote: Counting objects: 338, done. remote: Compressing objects: 100% (145/145), done. remote: Total 338 (delta 101), reused 323 (delta 89) Receiving objects: 100% (338/338), 71.46 KiB, done. Resolving deltas: 100% (101/101), done. From https://bitbucket.org/vim-plugins-mirror/vim-surround.git * branch main -} FETCH_HEAD Added dir '.vim/bundle/tpope-vim-surround'

ご覧のとおり、ここでは vim-surround リポジトリの全履歴を 1 つにスカッシュした merge commit を記録しています。

1bda0bd [3 minutes ago] (HEAD, stree) Merge commit 'ca1f4da9f0b93346bba9a430c889a95f75dc0a83' as '.vim/bundle/tpope-vim-surround' [Nicola Paolucci] ca1f4da [3 minutes ago] Squashed '.vim/bundle/tpope-vim-surround/' content from commit 02199ea [Nicola Paolucci]

この後で upstream リポジトリからプラグインのアップデートを行う場合は、単に subtree pull を行えばよいでしょう。

 git subtree pull --prefix .vim/bundle/tpope-vim-surroundhttps://bitbucket.org/vim-plugins-mirror/vim-surround.git main --squash

この方法は簡便ではあるが、ややコマンドが長く覚えにくいのが欠点です。サブプロジェクトをリモートとして作成することにより、コマンドを短くすることができます。

サブプロジェクトをリモートとして追加する

サブツリーをリモートとして追加すると、さらに短い形式で参照できるようになります。

git remote add -f tpope-vim-surround https://bitbucket.org/vim-plugins-mirror/vim-surround.git

これで (以前と同様に) サブツリーを追加できますが、短い形式でリモートを参照できるようになりました。

 git subtree add --prefix .vim/bundle/tpope-vim-surroundtpope-vim-surround main --squash

サブプロジェクトを後の日付でアップデートするコマンドは次のようになります。

 git fetch tpope-vim-surround main git subtree pull --prefix .vim/bundle/tpope-vim-surroundtpope-vim-surround main --squash

上流に戻す

これで、ローカルの作業ディレクトリにあるサブプロジェクトに修正を自由にコミットできます。

ローカルのコントリビューションを upstream プロジェクトに戻す場合は、そのプロジェクトをフォークして別のリモートとして作成する必要があります。

git remote add durdn-vim-surround ssh://git@bitbucket.org/durdn/vim-surround.git

ここで、次のように subtree push コマンドを実行することができます。

 git subtree push --prefix=.vim/bundle/tpope-vim-surround/durdn-vim-surround main git push using: durdn-vim-surround main Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 308 bytes, done. Total 3 (delta 2), reused 0 (delta 0) To ssh://git@bitbucket.org/durdn/vim-surround.git 02199ea..dcacd4b dcacd4b21fe51c9b5824370b3b224c440b3470cb -} main

これが完了すると、パッケージ開発全体のメンテナーに対して pull-request を送る準備が整います。

subtree コマンドを使用しない方法

git subtree は、サブツリーのマージ戦略とは異なるものです。何らかの理由で git subtree が利用できない場合であっても、マージ戦略を利用することは可能です。ここではその手順を説明します。

依存関係を単純な git remote として追加します。

git remote add -f tpope-vim-surround https://bitbucket.org/vim-plugins-mirror/vim-surround.git

依存関係の内容をリポジトリに読み込む前に、この時点までのプラグインのツリー履歴全体を追跡できるように、マージを記録することが重要です。

 git merge -s ours --no-commit tpope-vim-surround/main

これにより次のような出力が得られます:

Automatic merge went well; stopped before committing as requested

次に、最新のツリー オブジェクトの内容をプラグイン リポジトリに読み込み、コミット可能な作業ディレクトリに格納します。

 git read-tree --prefix=.vim/bundle/tpope-vim-surround/-u tpope-vim-surround/main

これでコミット可能になります (また、これが読み込んだツリーの履歴を保存するマージ コミットになります)。

git ci -m"[subtree] adding tpope-vim-surround" [stree 779b094] [subtree] adding tpope-vim-surround

プロジェクトのアップデートを行う場合は、subtree マージ戦略を使用して pull を実行すればよいでしょう。

 git pull -s subtree tpope-vim-surround main

結論

私はしばらくの間 submodule も使ってみましたが、subtree では submodule で発生するほとんどの問題が解決されているため git subtree の方を格段に高く評価しています。いつものことですが、git の機能はどれも活用するにはラーニング カーブに沿った経験が必要なのです。

Git を使いこなす方法について詳しくは、@durdn で私、または @Bitbucket チームをフォローしてください。

Git を学習する準備はできていますか?

この対話式チュートリアルを利用しましょう。

今すぐ始める