Git subtree を使用した複数リポジトリ間での共通コンポーネント運用について

## はじめに

複数のリポジトリ間で統一しておきたいファイルをそれぞれのリポジトリで別々に管理していた場合、変更や修正を各リポジトリそれぞれで行う必要があり、リポジトリ間での差分発生や変更の反映漏れなどの可能性があります。
本記事では、複数のリポジトリ内で共通のファイルやコンポーネントを使い回したい時の解決方法として、[Git subtree](https://github.com/git/git/blob/v2.0.0/contrib/subtree/git-subtree.txt) を使用した解決方法を提示します。

### Git subtree を 30 秒で（なんとなく）理解する

シンプルに説明すると、
「**プロジェクト内の特定のディレクトリのみ、別のリモートリポジトリにも push/pull することができるようになる**」
というようなイメージが近そうです。

共通で使用したいファイルをディレクトリ単位で別リポジトリに切り離し、`git subtree` コマンドを使用することでファイルの共通化を図ります。
例として 『**Nuxt を使用した複数プロジェクト間での components ディレクトリの共通化**』 を考えていきます。

### 本記事での仮定

共通ファイル（今回は components）を管理するリポジトリと、2 つ以上の Nuxt プロジェクトを管理するリポジトリを用意したと考え、話を進めていきます。
また Nuxt プロジェクト A/B に関しては、`create-nuxt-app（v3.4.0）` にてプロジェクトを作成したものと同等のディレクトリ構成とします。

- 共通コンポーネントリポジトリ
  - プロジェクトルート直下に `.vue` 拡張子の単一コンポーネントファイルが保存されます。
- Nuxt プロジェクト A
- Nuxt プロジェクト B


## subtree の設定方法

Git subtree によるコンポーネントの共通管理は、大きく分けて 2 step で設定できます。
共通コンポーネントを使用するプロジェクト A/B にて、subtree の設定を行います。

### step 1. リモートリポジトリの設定

**プロジェクト A/B のリポジトリに共通コンポーネントリポジトリをリモートリポジトリとして設定します。**

```js
$ git remote add common-components {{ リポジトリのURL }}
```


リポジトリの URL は clone とかの時に指定するやつです。また `common-components` 部分は、任意の名前で指定できます。
設定されたかどうかは `$ git remote` で確認でき、今回の例では origin と別に common-components がリモートリポジトリとして追加されていれば OK です。

### step 2. subtree の設定

**リモートリポジトリに追加した `common-components` を、各プロジェクトの subtree として設定します。**

共通管理したいのは components ディレクトリ配下のみですので、そのディレクトリのみで push/pull を行うことができるように subtree として設定を行います。
以下のコマンド実行時に `--prefix=components` によって subtree 用の components ディレクトリが作成されるため、 `create-nuxt-app` で自動生成された components ディレクトリは一度削除します。

```js
$ git subtree add --prefix=components --squash common-components main
```

step 1 で指定した共通コンポーネントリポジトリ （`common-components`）の main ブランチの中身が components ディレクトリとして展開されます。
これで共通コンポーネントリポジトリへの subtree push/pull を行うことができるようになりました。

なお、プロジェクト内で共通コンポーネント編集時の commit や origin への push/pull は普通にできます。
あくまで **subtree として設定されているリポジトリにも push/pull ができるようになった**というだけです。

## subtree の push/pull コマンド

**origin への push/pull とは別に、サブツリー用の push/pull コマンドがあります。**

origin への push/pull では、プロジェクトのリモートリポジトリには変更が保存されるものの、subtree 側には変更が反映されないため、共通コンポーネントを変更した場合は subtree push/pull を行い、共通コンポーネントのリモートリポジトリを更新します。

```js
// プロジェクトの変更を subtree に反映する
$ git subtree push --prefix=components {{ step.1 で設定したリポジトリの名称 }} main

// subtree の変更をプロジェクトに取り込む
$ git subtree pull --prefix=components {{ step.1 で設定したリポジトリの名称 }} main
```

プロジェクト A で subtree push を行なった後、プロジェクト B で subtree pull を実行することでコンポーネントの変更を取り込むことができます。
なお各プロジェクトに subtree として登録されている共通コンポーネントのリポジトリでは、通常通り `$ git pull` コマンドによって各プロジェクトからの変更をプルします。

## プロジェクト間での作業手順を整理（仮）

Git-Flow だとこのような運用が良いのではないか、と考えた程度のものですので参考までに。
運用していく上でより良い方法があれば更新予定です。

1. プロジェクト A のトピックブランチでコンポーネントの追加・変更作業を行い、PR（プルリク）を作成
2. PR 承認後、main にトピックブランチがマージされたタイミングでプロジェクト A から subtree にプッシュ
3. プロジェクト B で subtree pull 用のトピックブランチを作成し、subtree から共通コンポーネントの追加・変更分をプル
4. コンポーネント追加分を取り込み、PR 作成
5. プロジェクト B でレビュー完了後、main にマージ

![1920 Web – 2@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390806/93c36e57-e420-5bac-12e0-9e6e85515592.png)


### 運用上の課題
コンポーネントを追加・変更する上で外部パッケージを追加し、`package.json` を変更した場合、subtree の管理外ですので各プロジェクトごとに `package.json` を変更する必要があります。
プロジェクト間で連絡を取り合って解決するか、components ディレクトリ内に `README.md` などを置き、パッケージの追加時に記載するなどの運用を行なった方が良いかもしれません。

## 参考

- [Git subtree](https://github.com/git/git/blob/v2.0.0/contrib/subtree/git-subtree.txt)
- [【ATLASSIAN Blog】 Git Submodule の代替: Git Subtree](https://www.atlassian.com/ja/blog/alternatives-to-git-submodule-git-subtree)
- [【ATLASSIAN Blog】 Git subtree のパワー](https://www.atlassian.com/ja/blog/the-power-of-git-subtree)
- [【手順】git subtreeコマンドの使い方](https://qiita.com/takahashi-kazuki/items/0c34b3bc5da6700d38a5)