0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

submodule の追加操作をなくす git subtree 入門

0
Posted at

はじめに

別のリポジトリで管理しているライブラリを、自分のプロジェクトに取り込みたいことがあります。最初に候補へ挙がるのは git submodule です。ただし submodule は、clone した人が git submodule update --init を実行しないとディレクトリが空のままになります。

この追加操作をなくしたいときの選択肢が git subtree です。この記事では、git subtree の用途、コマンド、仕組み、submodule との違いをまとめます。

開発環境

  • Windows 11
  • Git 2.55.0
  • VSCode

git subtree とは

他のGitリポジトリの内容を、自分のリポジトリのサブディレクトリへコピーするコマンドです。取り込んだ後は、そのディレクトリは普通のファイル群として扱えます。clone した人は追加の操作なしでファイルを取得できます。

用途は次の4つが中心です。

やりたいこと 使うコマンド
社内共通ライブラリを複数プロジェクトで使う git subtree add
取り込み元の更新を反映する git subtree pull
取り込み先で直した内容を本家へ戻す git subtree push
モノレポの1ディレクトリを独立リポジトリにする git subtree split

生まれた経緯

Git 本体には以前からサブツリーマージ戦略(git merge -s subtree)がありました。これを使えば別リポジトリの内容をサブディレクトリへ配置できます。ただし取り込み、更新、切り出しのそれぞれで手順が長く、正確に覚えて実行する必要がありました。

2009年に Avery Pennarun 氏が、この手順をラップする外部コマンド git-subtree を公開します。これが Git 1.7.11(2012年6月)から contrib/subtree として Git に同梱されました。

仕組み

addsplit の2つが理解するうえでポイントになると思います。残りのコマンドは組み合わせで理解できます。

add がすること

  1. 取り込み元リポジトリの履歴を fetch する
  2. 取り込んだ内容を --prefix で指定したディレクトリ配下へ配置するマージコミットを作る

--squash を付けると、取り込み元の全コミットを1つにまとめてから配置します。自分のリポジトリの git log に他人のコミットが並ぶのを防げます。

split がすること

--prefix 配下のファイルを変更したコミットだけを抜き出します。抜き出したコミットは、そのディレクトリをルートとするパスへ書き換えられます。結果は新しいブランチとして作られます。

git subtree push は、内部で split を実行してから push しています。

主要なコマンド

shared-utils というリポジトリを libs/shared-utils に取り込む例で書きます。

remote を登録する

毎回URLを書くと長いので、remote 名を付けておきます。

git remote add shared-utils https://github.com/example/shared-utils.git

add:取り込む

git subtree add --prefix=libs/shared-utils shared-utils main --squash

--prefix には、まだ存在しないディレクトリを指定します。

$ ls libs/shared-utils
README.md  package.json  src/

pull:取り込み元の更新を反映する

git subtree pull --prefix=libs/shared-utils shared-utils main --squash

push:変更を本家へ送る

libs/shared-utils 配下を直したコミットだけが、指定ブランチへ送られます。

git subtree push --prefix=libs/shared-utils shared-utils feature/fix-typo

送った先で Pull Request を作る流れになります。

split:ディレクトリを独立させる

git subtree split --prefix=libs/shared-utils -b shared-utils-only

shared-utils-only ブランチができます。元の libs/shared-utils/src/index.ts は、このブランチでは src/index.ts になります。

split は既存のブランチを書き換えません。新しいブランチを作るだけなので、試して失敗してもブランチを削除すれば元に戻ります。

submodule との比較

項目 git subtree git submodule
取り込んだファイルの実体 自分のリポジトリに入る 入らない(コミットIDの参照だけ)
clone 直後の状態 ファイルが揃っている 空ディレクトリ
利用側に必要な追加操作 なし git submodule update --init
設定ファイル なし(毎回 --prefix を指定) .gitmodules
リポジトリのサイズ 取り込み元の履歴分だけ増える ほぼ増えない
取り込み元へ変更を戻す git subtree push サブモジュール側で commit / push
バージョンの指定 取り込んだ時点の内容で固定 コミットIDで固定

メリット

  • clone した人が subtree を知らなくても使えます。ファイルが最初から存在します
  • CI やビルドツールの設定を変えずに済みます。ただのディレクトリとして見えます
  • 取り込み元のリポジトリが消えても、ファイルは自分のリポジトリに残ります
  • 取り込み先で直した内容を、git subtree push で本家へ送れます

チーム全員にGitの追加コマンドを説明しなくてよいのが、実務では一番ありがたいと思います。

注意点

注意点 内容
--squash の有無を統一する add で付けたなら pull でも毎回付けます。混ぜると pull でコンフリクトします
--prefix を毎回書く 設定ファイルがないため、パスを間違えると別の場所にファイルが展開されます
push に時間がかかる split が全コミットを走査します。履歴が長いリポジトリでは数分かかります
履歴が混ざる 取り込み元のコミットが自分の git log に並びます。--squash で1件に減らせます
リポジトリが大きくなる 取り込み元のファイルと履歴を自分のリポジトリが保持します
どこから取り込んだか記録が残らない コミットメッセージにしか情報がありません。READMEへ prefix とURLを書いておきます

最後の点は運用でカバーするしかないので、私は次のようなメモをリポジトリの README に置いています。

## 取り込んでいる外部リポジトリ

| prefix | 取り込み元 | ブランチ | squash |
|---|---|---|---|
| libs/shared-utils | https://github.com/example/shared-utils.git | main | あり |

選び方

状況 選ぶもの
npm や PyPI で配布されている パッケージマネージャ
clone する人にGitの追加操作をさせたくない subtree
OSSをフォークして自社の改造を当て続ける subtree
取り込み元のバージョンをコミットID単位で固定したい submodule
リポジトリのサイズを増やしたくない submodule
取り込み元がプライベートで、閲覧権限を分けたい submodule

まとめ

この記事では git subtree について記載しました。他のリポジトリの内容を自分のリポジトリのサブディレクトリへコピーするコマンドで、clone した人に追加の操作をさせずに済みます。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?