~nabeken/diary/

Gentoo Linux(6年くらい)とFreeBSD(1年くらい)とOpenBSD(新参者)を使う日々。


IHANet BGP peering overview

Gitリポジトリの特定のディレクトリ以下のみを含んだ新しいリポジトリを作成する

Posted on Sat Feb 06 23:13:35 +0900 2010 by nabeken

使用中のGitリポジトリからあるディレクトリ以下のみを含む新しいリポジトリを作成する方法を検討しました。

twitterでつぶやいたところ、 @n_iwamatsu さんに git-commit-tree というコマンドを教えてもらいました。(参考: http://twitter.com/n_iwamatsu/status/2304942015)

このコマンドは引数に tree-ish と親コミットオブジェクトの commit-ish (optional)、そして標準入力からコミットログを取りそれらに紐付いた新たなコミットオブジェクトを作成し、その commit-ish を標準出力へ出力します。

ここで、Gitにおけるディレクトリの扱いについて復習です。ディレクトリはGitではTreeオブジェクトとして表現されます。つまり、ある特定のディレクトリ以下の履歴を抜き出すということは、コミットオブジェクト内のある特定のTreeオブジェクトをだけ抜き出し、新たにそのTreeオブジェクトのみを持つコミットオブジェクトを作ることに等しいと言えます。そこで、さきほどの git-commit-treeを使用します。

コミットオブジェクトは初期状態から順にコミットオブジェクトを繋げていきます。あるディレクトリの最初のコミットを調べるには git log コマンドを使用します。

 $ git log --reverse hoge/

--reverseを使用したため、通常の降順ではなく、昇順になります。したがって、一番上のコミットが最初のコミットになります。このときの commit-ish をメモします。次に git ls-tree で hoge ディレクトリの Tree オブジェクトの tree-ish を調べます。

 $ git ls-tree commit-ish | grep hoge/
040000 tree 036546a8370c39f90c42ceccc64433f7603e1ca0   hoge

左から3つ目が hoge ディレクトリに対応した Tree オブジェクトの tree-ish です。これで、 git-commit-tree に必要な情報は揃いました。

 $ echo test commit | /usr/libexec/git-core/git-commit-tree 036546a8370c39f90c42ceccc64433f7603e1ca0
78b954a79e1e24d66cc373dcba094d089f0a0e4c

初回の commit-tree はルートコミットオブジェクトを作りますので、親コミットオブジェクトの commit-ish は指定しません。出力された commit-ish を確認してみます。

 $ git log --format=raw 78b954a79e1e24d66cc373dcba094d089f0a0e4c
commit 78b954a79e1e24d66cc373dcba094d089f0a0e4c
tree 036546a8370c39f90c42ceccc64433f7603e1ca0
author TANABE Ken-ichi  1245855750 +0900
committer TANABE Ken-ichi  1245855750 +0900

    test commit

tree で表示されている tree-ish が git-commit-tree の引数と一致していることを確認してください。これがルートコミットオブジェクトとなります。あとは、この作業を繰り返すことで、あるディレクトリを含むTreeオブジェクトを持つコミットオブジェクトを順に繋げることができます。

2度目以降の git-commit-tree では、親コミットオブジェクトを指定します。今回の例では、2度目は git log --reverse hoge/ の2つ目のコミットオブジェクトから同様に hoge/ の tree-ish を見つけ、git-commit-treeの引数にします。

 $ echo test commit 2 | /usr/libexec/git-core/git-commit-tree tree-ish -p 78b954a79e1e24d66cc373dcba094d089f0a0e4c
f7d807c4814f81be73e7b6cceb99cef45ed0a8c5

 $ git log --format=raw f7d807c4814f81be73e7b6cceb99cef45ed0a8c5
commit f7d807c4814f81be73e7b6cceb99cef45ed0a8c5
tree 8ddee3c3430b3d4039b7e37ff59f96cb8fc83f10
parent 78b954a79e1e24d66cc373dcba094d089f0a0e4c
author TANABE Ken-ichi  1245856043 +0900
committer TANABE Ken-ichi  1245856043 +0900

    test commit 2

commit 78b954a79e1e24d66cc373dcba094d089f0a0e4c
tree 036546a8370c39f90c42ceccc64433f7603e1ca0
author TANABE Ken-ichi  1245855750 +0900
committer TANABE Ken-ichi  1245855750 +0900

    test commit

きちんとコミットオブジェクトが繋がっているのが確認できます。

git2git.sh

もちろん、手作業でやるにはあまりにも面倒なので、これを自動化するスクリプトを作成しました。以下で公開しています。

使い方は、既存リポジトリ上で

 $ git2git.sh hoge/ 新たなコミットオブジェクトを作成するブランチ名

を実行すると、最終的に一番新しいコミットオブジェクトの commit-ish が指定したブランチになります。あとは、新規作成したリポジトリで、作成したブランチを git fetch すれば完了です。詳しくは README を参照してください。

git filter-branch

ブックマークコメントで、git filter-branchを使えば、というコメントがありました(Gitの中の人より!!)。ググるとPro Gitの記事が見つかりました。

みなさん、こっちのほうが簡単確実ですよ!