イラスト: git branch

Git サブモジュール

リストに戻る

Git サブモジュールによって、Git リポジトリを別の Git リポジトリのサブディレクトリとして保持できます。Git サブモジュールは、別のリポジトリの特定の時点におけるスナップショットに対する単なる参照です。Git サブモジュールによって、Git リポジトリで外部コードのバージョン履歴を組み込んで追跡できます。

Git サブモジュールとは

多くの場合、コード リポジトリは外部コードに依存します。この外部コードは、いくつかの異なる方法で組み込めます。外部コードはメイン リポジトリに直接コピー & ペーストできます。この方法には、外部リポジトリに対するアップストリームの変更がすべて失われるというデメリットがあります。外部コードを組み込むもう 1 つの方法は、Ruby Gems や NPM のような言語のパッケージ管理システムを使用することです。この方法には、元のコードがデプロイされるすべての場所でインストールとバージョン管理が必要になるというデメリットがあります。これらの推奨される組み込み方法は、どちらも外部リポジトリに対する編集や変更を追跡できません。

git サブモジュールは、別の外部リポジトリ内において特定のコミットを指すホスト Git リポジトリ内のレコードです。サブモジュールは非常に静的で、特定のコミットのみを追跡します。サブモジュールは git ref やブランチを追跡せず、ホスト リポジトリが更新されても自動更新されません。サブモジュールをリポジトリに追加すると、新しい .gitmodules ファイルが作成されます。.gitmodules ファイルには、サブモジュール プロジェクトの URL とローカル ディレクトリの間のマッピングに関するメタデータが含まれています。ホスト リポジトリに複数のサブモジュールがある場合、.gitmodules ファイルにはサブモジュールごとにエントリがあります。

Git サブモジュールを使用するタイミング

厳密なバージョン管理を外部の依存関係に実施する必要がある場合は、git サブモジュールの使用を是非ご検討ください。次で、git サブモジュールの最適なユースケースをいくつか紹介します。

  • 外部コンポーネントまたはサブプロジェクトの変更が速すぎる、または今後の変更によって API が壊れる場合は、安全のためにコードを特定のコミットにロックできます。
  • あまり頻繁に更新されないコンポーネントがあり、それをベンダーの依存関係として追跡する場合があります。
  • プロジェクトの一部をサード パーティに委任して、その作業を特定の時間またはリリースで統合する場合。これも更新があまり頻繁でない際に機能します。

git サブモジュールの共通コマンド

git サブモジュールを追加する

git submodule add は、新しいサブモジュールを既存リポジトリに追加するために使用されます。次は、空リポジトリを作成して git サブモジュールを調べる際の例です。

$ mkdir git-submodule-demo
$ cd git-submodule-demo/
$ git init
Initialized empty Git repository in /Users/atlassian/git-submodule-demo/.git/

この一連のコマンドによって、新しいディレクトリ git-submodule-demo を作成してそのディレクトリに入り、新しいリポジトリとして初期化します。次に、サブモジュールをこの新しいリポジトリに追加します。

$ git submodule add https://bitbucket.org/jaredw/awesomelibrary
Cloning into '/Users/atlassian/git-submodule-demo/awesomelibrary'...
remote: Counting objects: 8, done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 8 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (8/8), done.

git submodule add コマンドは、git リポジトリを指す URL パラメーターを取ります。ここでは、awesomelibrary をサブモジュールとして追加しました。Git はサブモジュールをすぐにクローンします。これで、git status によってリポジトリの現在の状態を確認できるようになりました。

$ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

new file:   .gitmodules
new file:   awesomelibrary

2 つの新しいファイルが、リポジトリの .gitmodulesawesomelibrary ディレクトリに追加されました。.gitmodules の内容を確認すると、新しいサブモジュール マッピングが表示されます。

[submodule "awesomelibrary"]
path = awesomelibrary
url = https://bitbucket.org/jaredw/awesomelibrary
$ git add .gitmodules awesomelibrary/
$ git commit -m "added submodule"
[main (root-commit) d5002d0] added submodule
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 awesomelibrary

Git サブモジュールのクローン作成

git clone /url/to/repo/with/submodules
git submodule init
git submodule update

git submodule init

git submodule init の既定の動作は、マッピングを .gitmodules ファイルからローカルの ./.git/config ファイルにコピーすることです。これは冗長に思えるかもしれませんし、git submodule init の有用性に疑問を抱くかもしれません。git submodule init には、明示的なモジュール名のリストを受け入れる拡張動作があります。これによって、リポジトリ上の作業に必要な特定のサブモジュールのみをアクティブ化するワークフローが可能になります。これは、多数のサブモジュールがリポジトリにあっても、実行中の作業のためにすべてのサブモジュールを取得する必要がない場合に役立ちます。

サブモジュール ワークフロー

サブモジュールが親リポジトリ内で適切に初期化されて更新されると、スタンドアロン リポジトリとまったく同じように利用できます。つまり、サブモジュールには独自のブランチと履歴があります。サブモジュールに変更を加える際は、サブモジュールの変更を公開してから親リポジトリのサブモジュールへの参照を更新することが重要です。awesomelibrary の例を取り上げて、いくつか変更を加えていきます。

$ cd awesomelibrary/
$ git checkout -b new_awesome
Switched to a new branch 'new_awesome'
$ echo "new awesome file" > new_awesome.txt
$ git status
On branch new_awesome
Untracked files:
  (use "git add <file>..." to include in what will be committed)

new_awesome.txt

nothing added to commit but untracked files present (use "git add" to track)
$ git add new_awesome.txt
$ git commit -m "added new awesome textfile"
[new_awesome 0567ce8] added new awesome textfile
 1 file changed, 1 insertion(+)
 create mode 100644 new_awesome.txt
$ git branch
  main
* new_awesome

ここでは、ディレクトリを awesomelibrary サブモジュールに変更しました。いくつかの内容を含む新しいテキスト ファイル new_awesome.txt を作成して、この新しいファイルをサブモジュールに追加してコミットしました。ここで、ディレクトリを親リポジトリに戻して親リポジトリの現在の状態を確認しましょう。

$ cd ..
$ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

modified:   awesomelibrary (new commits)

no changes added to commit (use "git add" and/or "git commit -a")

git status を実行すると、親リポジトリが awesomelibrary サブモジュールへの新しいコミットを認識していることがわかります。これはサブモジュール リポジトリの責任であるため、特定の更新については詳述しません。親リポジトリは、サブモジュールをコミットに固定することだけを考慮しています。これで、git addgit commit をサブモジュールで実行することで、親リポジトリを再び更新できます。これによって、すべてがローカル コンテンツで良好な状態になります。チーム環境で作業している場合は、サブモジュールと親リポジトリの各更新を git push することが重要です。

サブモジュールを扱う際の混乱とエラーの一般的なパターンは、リモート ユーザーに更新をプッシュし忘れることです。たった今行った awesomelibrary の作業を再確認すると、親リポジトリの更新のみがプッシュされていました。別の開発者が最新の親リポジトリをプルすると、その親リポジトリは、私たちがサブモジュールをプッシュし忘れたために、これらの開発者がプルできない awesomelibrary のコミットを指し示していることになります。これによって、リモートの開発者のローカル リポジトリが壊れます。この失敗シナリオを回避するには、必ずサブモジュールと親リポジトリをコミットしてプッシュするようにしてください。

結論

Git サブモジュールは、git を外部の依存関係管理ツールとして活用する強力な方法です。Git サブモジュールは詳細機能でありチーム メンバーが習得するまで時間がかかる場合があるため、使用前にその長所と短所を比較検討してください。

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

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

今すぐ始める