ディレクトリ単位でgit clone/fetchする方法を探していました。sparsecheckoutを利用するという方法も紹介されいましたが、その方法の場合、「最初に普通にcloneする必要がある」というところが、いま探している用途には向いていなかったので別の方法を探してみました。
Gitの内側
まずは、Gitの仕組みを調べるところから始めました。Gitの内側を読むと、Gitの中でどのように変更履歴やディレクトリ構造が保存されているのかがよくわかりました。
- 変更履歴・ディレクトリ構造を、commit object, tree object, blob objectのグラフで表現している。
- tree objectには、ディレクトリ中にある、ディレクトリ名やファイル名が保存される。
- blob objectには、ファイルのコンテンツが保存される。
- cloneするときは、指定したbranchやclone元レポジトリのHEADに紐づく、objectを取得することになる。
- shallow cloneするときは、branch名等で指定されたcommitの親は辿らずに、参照できるコミットを取得するのだろう。
shallow cloneしたとき図(赤黄緑だけcloneする)
ディレクトリ単位でcloneする方法
Gitの内側を調べてみると、cloneする対象は、指定されたcommit objectとから辿ることが出来る範囲にしぼられることが分かります。ということは、cloneしたいディレクトリ以下のみを辿るcommit objectを作っちゃえば、ディレクトリ単位でgit cloneできるんじゃないかと考えました。
具体的には、clone元のレポジトリで、cloneしたいディレクトリのtree objectのsha1を調べて、そのobjectを指すcommit objectを作り、さらに、そのcommitを指すブランチやタグを作ります。そしてcloneする際は、作成したブランチを指定してcloneしてやります。
以下、bashでこの操作をやったときの例。
# clone元レポジトリで事前にやっておくこと。
declare BASE_HASH="692c6c9"
declare TARGET_DIR="directory_name"
TREE_HASH=$(git rev-parse $BASE_HASH:$TARGET_DIR)
COMMIT_HASH=$(git commit-tree $TREE_HASH -m 'clone')
git tag -a clone_tag -m 'clone_tag' $COMMIT_HASH
# cloneするときの操作
git clone -b clone_tag (clone元レポジトリのurl)
ここでは、cloneで書きましたが、作成したtagやブランチを指定して、fetch/checkoutすることも出来ます。ただ、任意のディレクトリをclone出来るというやり方ではないので、チーム内で運用ルールを決めるなり、何かのツールの形にまとめるなりといった工夫は必要になりそうです。例えば。
- コミットする際、上記の操作を自動で行い、pushするときclone_tagも一緒にpushするスクリプトを準備するとか.
- clone元レポジトリにhookを仕込んで、pushされたら、上記操作を行うとか。
最後に、その他の懸念点を。
- 親が居ないcommitだと、90日後にgit gcの対象になって消されてしまうかもしれない。確認してないです。
- 自分の場合、データを取得することのみが必要だったのでこの方法で大丈夫でしたが、 「ディレクトリ単位で取得し、変更コミット、元のブランチにマージ」と言った用途の場合、もうひと工夫必要になりそう。