Git で特定のブランチを別リポジトリとして分割する

# TL;DR

- Gitで複数のプロジェクトが1つのリポジトリに混在し**ブランチ＝プロジェクト**という扱いとなってしまった場合に、**履歴を残した状態で**ブランチを別のリポジトリに分割する方法を記載します。

# 前提

- リモートリポジトリへの変更の反映は行われていること。

# 環境

- Windows 10 Pro x64 1909 (OS ビルド 18363.1139)
- git version 2.28.0.windows.1
- [Azure DevOps Server 2019 Update1](https://docs.microsoft.com/en-us/azure/devops/server/release-notes/azuredevops2019u1?view=azure-devops)

# 構成

|名称|値|
|---|---|
|分割元リポジトリ|repoA|
|分割元ブランチ|repoA-branchB|
|分割元サブフォルダ|repoA-branchB-folderC|
|分割先リポジトリ|repoB|


## 変更前

```
repoA  <--> http://hogehoge/repoA.git
├─master
└─remotes/origin
    ├─branchB
    │  └─folderC
    │      ├─subfolderD
    │      ├─subfolderE
    │      └─subfolderF
    └─master
```

## 変更後

`branchB`の`folderC`をrepoBとして分割する。

### 分割元

```
repoA  <--> http://hogehoge/repoA.git
├─master
└─remotes/origin
    └─master
```

### 分割先

```
repoB  <--> http://hogehoge/repoB.git
└─master
    ├─subfolderD
    ├─subfolderE
    └─subfolderF
```

# 手順

1. コマンドプロンプト、またはGit Bashを開きます。

2. 作業フォルダを新しいリポジトリを作成したい場所に移動します。

3. 分割したいフォルダのあるリポジトリをクローンします。

    ```bat
    git clone https://hogehoge/repoA
    ```

4. 作業フォルダをクローンしたリポジトリに移動します。

    ```bat
    cd repoA
    ```

5. ブランチを切り替えます。

    ```bat
    git checkout branchB
    ```

6. 現在のブランチが切り替わっていることを確認します。

    ```bat
    git branch
    > * branchB
    >   master
    ```

    :::note
    特定のブランチを分割するためブランチを移動しています。
    :::


7. 作業フォルダをリポジトリの親フォルダに移動します。

    ```bat
    cd ..\
    ```

8. ローカルでリポジトリをクローンします。

    ```bat
    git clone repoA repoB
    ```

9. 作業フォルダをクローンしたリポジトリに移動します。

    ```bat
    cd repoB
    ```

10. ローカルブランチが切り替えたブランチのみになっていることを確認します。

    ```bat
    git branch
    > * branchB
    ```

    :::note
    ブランチを切り替えてからローカルでリポジトリをクローンすることで、切り替えたブランチのみとなります。
    :::

11. 分割したいフォルダが一つのフォルダにまとまっていない場合は、`git mv`コマンドで移動してフォルダにまとめます。

    [覚えておくと便利なGitコマンド - Qiita](https://qiita.com/yoshie8423/items/8ba2b06e99c8749d6855)

    :::note
    エクスプローラーで移動してもリポジトリには移動としては記録されません（削除と追加となります）ので、`git mv`コマンドを使用する必要があります。
    :::

12. リポジトリのファイルからサブフォルダを抽出するには、以下の情報を指定して git filter-branch を実行します。
   
    ```bat:例文
    git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME HEAD
    ```
   
    |||
    |--|--|
    |`FOLDER-NAME`|別のリポジトリの作成元にしたいプロジェクト内のフォルダです。|
    |`--prune-empty`|コメントのみの空commitを消去してくれます。|

    ```bat:実施
    git filter-branch --prune-empty --subdirectory-filter folderC HEAD
    ```
   
13. repoAのルートに配置された必要な`.gitignore`があった場合にはリポジトリに追加します。
   
    ```bat
    git add .gitignore
    git commit -a -m "任意のコメント"
    ```
   
14. リモートリポジトリ上で新しいリポジトリを作成し、リモートリポジトリ URLをコピーします。

    ・[Azure DevOps (Server) の手順](https://docs.microsoft.com/ja-jp/azure/devops/repos/git/create-new-repo?view=azure-devops-2019)
    　チームプロジェクト内に複数のリポジトリを作成することが可能です。
    ・[GitHubの手順](https://docs.github.com/ja/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/creating-a-new-repository)

15. リポジトリの既存のリモート名を確認します。
   
    ```bat
    git remote -v
    > https://hogehoge/repoA (fetch)
    > https://hogehoge/repoA (push)
    ```

16. 既存のリモート名および新しいリモートリポジトリ URL を使って、新しいリポジトリの新しいリモート URL をセットアップします。
   
    ```bat
    git remote set-url origin https://hogehoge/repoB
    ```

17. リモートURLが変更されたことを確認します。
   
    ```bat
    git remote -v
    > https://hogehoge/repoB (fetch)
    > https://hogehoge/repoB (push)
    ```

18. ブランチ名を変更します。
   
    ```bat
    git branch -m branchB master
    ```

19. ブランチを確認します。
   
    ```bat
    git branch
    > * master
    ```

20. リポジトリをリモートリポジトリにプッシュします。
   
    ```bat
    git push -u origin --all
    ```

21. ローカルの`remotes/origin`内の不要なブランチを消すため、フォルダを消して再取得します。
   
    ```bat
    git branch -a
    > * master
    >   remotes/origin/HEAD -> origin/branchB
    >   remotes/origin/branchB
    >   remotes/origin/master
    ```

    ```bat
    cd ..\
    ```
    
    エクスプローラーからrepoBフォルダを削除します。

    :::note
    誤操作を避けるため、CUIコマンドは記載していません。
    :::
    
    ```bat
    git clone git clone https://hogehoge/repoB
    ```
   
    ```bat
    cd repoB
    ```
   
    ```bat
    git branch -a
    > * master
    >   remotes/origin/HEAD -> origin/master
    >   remotes/origin/master
    ```

22. リモートリポジトリから分割済みのブランチ「branchB」を削除します。

    :::note warn
    ブランチ削除時は慎重に作業してください！
    :::

    ・[Azure DevOps (Server) の手順](https://docs.microsoft.com/ja-jp/azure/devops/repos/git/delete-branch?view=azure-devops)
    　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204150/8069f08d-4a4a-0600-5e1e-50b93957caa0.png)
    ・[GitHubの手順](https://docs.github.com/ja/free-pro-team@latest/github/administering-a-repository/deleting-and-restoring-branches-in-a-pull-request)
    ・[コマンドでの手順](https://qiita.com/mather314/items/a1536c52a2eb0426b2b5)



# 参考資料

- [サブフォルダを新規リポジトリに分割する - GitHub Docs](https://docs.github.com/ja/free-pro-team@latest/github/using-git/splitting-a-subfolder-out-into-a-new-repository)
- [git filter-branchで過去の全てのcommitから画像ファイルの追加/変更をなかったことにしてリポジトリを軽量化する - dskd](https://dskd.jp/archives/46.html)