🔖

SubmoduleとForkを用いたGit運用(プロジェクトの一元管理)について

に公開

はじめに

私の研究では様々なセンサーを搭載した船を動かし, データセットの作成を行っています.
用いているセンサードライバは外部のものを使用しているため外部のリポジトリをsubmodule登録して使用していました. しかしこの方法だと自分の手元で変更したときに問題が起きるため現在は

  1. 外部リポジトリをFork
  2. Forkしたものをsubmodule登録
  3. サブモジュールディレクトリで変更作業
  4. 親リポジトリ(一元管理の大本)でsubmoduleの更新をコミット

のようにして管理を行っています.

以下ではこれらの具体的な方法を解説した後, そもそもなぜこのような管理方法が必要なのか, そして最後にいくつかの注意点を紹介します.

1.外部リポジトリをFork

サブモジュール元のリポジトリを GitHub 上で fork する

たとえば:

元リポジトリ:https://github.com/元レポジトリのアカウント名/Repository名.git
↓
フォークリポジトリ:https://github.com/自分のAcount名/Repository名.git

のように元のリポジトリを一旦自分のアカウント内にForkします. これにより変更を行った際に自分のリポジトリとしてGit管理できるようになります.
また,ForkはすべてGithub上で行うことが出来ます.
参考: https://docs.github.com/ja/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo

2.Forkしたものをsubmodule登録

1から登録する場合

親リポジトリにて,

git subnmodule add <https://github.com/自分のAcount名/Repository名.git> <FilePath>

でいけます.

一度登録している外部リポジトリを自分のForkレポジトリに変更する場合

.gitmoduleの該当部分を

[submodule "<FilePath>"]
    path = <FilePath>
    url = https://github.com/元リポジトリのアカウント名/Repository名.git
    ↓
    url = https://github.com/自分のAcount名/Repository名.git

に変更でOKです.
このとき,.git/configにも反映しておくと良いです:

git submodule sync

3.サブモジュールディレクトリで変更作業

自分のForkリポジトリを登録するようにしたことで, 変更を手元にCommitするだけでなく以下のようにPushしてリモート保存までできるようになりました.

cd <FilePath>
git checkout -b my_custom_branch
# コードやパラメータを変更
git add .
git commit -m "Change parameters for my setup"
git push origin my_custom_branch

ただし, この状態ではForkリポジトリが変更されただけで親リポジトリは変わっていません.
そこで最後に,

4.親リポジトリでsubmoduleの更新をコミット

cd ../..  #階層は人それぞれです.親リポジトリのルートへ
git add <FilePath>
git commit -m "Update submodule to use modified branch"
git push

これにてSubmoduleの更新の登録が完了したため, 親リポジトリで一元管理が出来ている状態になります.

ここからは番外編でsubmoduleの詳細や, 更新時・クローン時の注意点などについて記述していきます.

番外編1.なぜこのような管理方法が必要なのか

ここではFork→Submodule登録→Commit&Pushの流れがなぜ必要なのかについて記述します.

Submodule登録は"commit単位で固定"される

これが最大の理由です.
Submodule登録とは指定したリポジトリの任意のcommitについて,そのhash値を取得し追跡可能にすることを言います(defaultでは最新コミットです).

たとえばカレーをスパイスから作るとき, その調合割合について参考にしたWebサイトのレシピをURLでメモしておくのがSubmodule登録です. 決してその投稿者の最新の調合を追いかけたいわけではないですよね.

基本的に外部リポジトリを持ってくるときも"そのレシピを持ってくる"ようにする方が良いのでこの仕組みになっています.

試しにGitHubのUI上でサブモジュールをクリックしてみてください

飛ばされた先のURLを見ると

https://github.com/Acount名/Repository名/tree(固定)/633d83eeb79ae09c077e169b1e9d7718ab17900d(ハッシュ値)

のようになっているはずです.やはりここにブランチ名は存在しません.

自分のカスタマイズは自分のところで管理

また上記の"Submoduleはレシピである"という理由から,変更を加えたい場合には外部リポジトリのSubmodule登録が適さないことが分かると思います.

他人のレシピを勝手に変えるのですからそれは自分で管理,公開してねというわけです.
この場合は自分のところにFork(レシピを自分のものとして引用)して, 変更した内容をComittとして保存,登録するのが妥当でしょう.

コレをSubmodule登録すれば自分オリジナルのレシピを別端末からでも再現可能だし,変更の都度Submodule登録を更新すれば一元管理(カレー全体のレシピ)と部分開発(スパイスの調合)を両立できるということになります.

更新の際のTips

Submodule登録で頻繁に更新を行いたいとき,自分のForkリポジトリの任意ブランチの最新コミットを登録する方法があります.
まず.gitmodulesを編集して以下のように:

[submodule "src/File名"]
    path = src/File名
    url = https://github.com/自分のAcount名/リポジトリ名.git
    branch = 任意のbranch名

とします.そして更新の際に

git submodule update --remote --merge

とします.これで.gitmodulesに設定したブランチの最新のcommitを取得し,現在のsubmoduleに反映できます.

番外編2.Trouble Shooting

その1. Submodule付きリポジトリのクローンで中身が空になってしまうヤツ

Submodule付きのリポジトリの場合,そのSubmoduleを引っ張ってくるかどうかを指定してあげないとデフォルトは引っ張ってこない設定になっています.
引っ張ってくるためにクローンの際に以下のオプションを追加してください

git clone --recurse-submodules https://github.com/Acount名/Repository名.git

のように--recurse-submodulesが必要です.
また,クローン時に忘れてしまった場合は後からでも

git submodule update --init --recursive

としてあげれば大丈夫です.

その2. 外部リポジトリの変更を取得したいとき

この場合は結構厄介です.
外部リポジトリ(元リポジトリ)のことをupstreamと呼びますが, upstreamからの更新は基本的にforkリポジトリ内で管理することになります.

Submodule登録の要領でforkリポジトリにupstreamを以下のように登録しておき,

cd <FilePath>
git remote add upstream https://github.com/外部Acount名/外部Repository名.git
git fetch upstream

upstreamに更新があった際はそのブランチと自分の作業ブランチを以下のようにmergeして取り込むことになります.

git checkout my_custom_branch  # 自分の作業ブランチ
git merge upstream/main        # 例:外部リポジトリの mainブランチ を取り込む

コンフリクトがあればここで解決します.
完了後は、my_custom_branch に「外部の更新 + 自分の変更」が統合された状態になります.

そして上記の3,4に沿って一元管理の親リポジトリとForkリポジトリのそれぞれに変更の保存をすれば完了です.

最後に

ここまでSubmoduleとForkを用いたプロジェクト一元管理のためのGit運用を紹介しました.

Submoduleは扱いが複雑でPushやPull,バージョン同期に注意が必要ですが,一元管理ができると依存関係の整合性を取りやすく「最新版で壊れた!」が起きづらかったり, チーム開発への拡張性があったりと便利だと思うので私自身どんどん活用していきたいと思います.

ほかにもパラメータの変更くらいであればそのままSubmodule登録しておいてCommitをローカルにだけ保存しておき親リポジトリに変更方法のREADMe.mdだけを追記しておくという方法もあるので,適したものを使っていきましょう.

岐阜大学アレックス研究室

Discussion