概要
gitリポジトリの運用にあたって、ある地点より以前の歴史(commit、blobなど)を一つのコミットにまとめたブランチを作成した上で、そのブランチに元のブランチの内容をマージして同期する方法を説明します。
例えば、あるリポジトリの内容について、一定以前の歴史を一つにまとめることで、リポジトリのサイズを抑えたり、共有したくない歴史を隠すためのブランチを作成して、別のremoteリポジトリにpushし、そのブランチに対して元リポジトリの変更をマージし続けるといった運用を想定しています。(そんな需要は滅多にないと思いつつ...)
手順
過去の歴史を一つにまとめた新しいブランチの作成
では、masterブランチの内容から、過去の歴史を一つにまとめた新しいブランチを作成していきます。
これが現状のブランチです。
今からmasterのHEADの内容をそのままに、歴史を潰した新たなブランチを作成します。
~/work/tmp/git-repo master
$ git log --oneline
02c4027 (HEAD -> master) a-5
95f6a9f a-4
91439fb a-3
14bbaa5 a-2
92c187d a-1
HEADの内容から新規コミットを作るために git commit-tree
を使います。このコマンドはツリーオブジェクトのハッシュを指定することで、指定されたtreeの内容から新規コミットを作成してくれます。( 02c4027^{tree}
はコミットオブジェクト02c4027に紐づくツリーオブジェクトを指定する記法)
こうすることでファイルの内容はそのままに、歴史を消した一つのコミットにすることができます。ここでは a-5 02c4027
のコミットまでの歴史を一つにまとめてみます。
~/work/tmp/git-repo master
$ echo 'init' | git commit-tree 02c4027^{tree}
f9ba9385fa4d03f0d9b97fdf172bab077e9b9a9f
歴史をまとめた一つのコミット f9ba938
が作成されました。
作成したコミットから mirror
ブランチを作成します。当然コミットは一つしかありません。またHEADのツリーオブジェクトの内容を引き継いでいるため、masterとの差分も存在しません。
~/work/tmp/git-repo master
$ git co -b mirror f9ba9385fa4d03f0d9b97fdf172bab077e9b9a9f
Switched to a new branch 'mirror'
# コミットは一つだけ。歴史が潰れている。
~/work/tmp/git-repo mirror
$ git log --oneline
f9ba938 (HEAD -> mirror) init
# 2wayでdiffを取っても差分が出力されない
~/work/tmp/git-repo mirror
$ git --no-pager diff master..mirror
~/work/tmp/git-repo mirror
masterブランチに追加されたコミットを同期する
ここからが本題です。上記で作成したmirrorに、masterに新しくコミットされた内容を同期していきます。
まずはmasterに適当な内容で2つほどコミット b-1
b-2
を追加します。
~/work/tmp/git-repo master!
$ echo "hello" > sample.txt & git add sample.txt & git commit -m "b-1"
[master 0397b61] b-1
1 file changed, 1 insertion(+)
create mode 100644 sample.txt
~/work/tmp/git-repo master!
$ echo "world" > hoge.txt & git add hoge.txt & git commit -m "b-2"
[master 6c5b4d7] b-2
1 file changed, 1 insertion(+)
create mode 100644 hoge.txt
先ほどmirrorを作成した時点のHEAD a-5 02c4027
から2つコミットが増えました。ファイル差分も発生しています。
~/work/tmp/git-repo master
$ git log --oneline
6c5b4d7 (HEAD -> master) b-2
0397b61 b-1
02c4027 a-5
95f6a9f a-4
91439fb a-3
14bbaa5 a-2
92c187d a-1
~/work/tmp/git-repo master
$ git diff master..mirror
diff --git a/hoge.txt b/hoge.txt
deleted file mode 100644
index cc628cc..0000000
--- a/hoge.txt
+++ /dev/null
@@ -1 +0,0 @@
-world
diff --git a/sample.txt b/sample.txt
deleted file mode 100644
index ce01362..0000000
--- a/sample.txt
+++ /dev/null
@@ -1 +0,0 @@
-hello
ではこのmasterをmirrorに取り込んでいきます。
まずはマージ用のブランチ merge-to-mirror-1
を作成します。
~/work/tmp/git-repo master
$ git checkout -b merge-to-mirror-1
Switched to a new branch 'merge-to-mirror-1'
masterとmirrorは共通祖先コミットを持たない歴史の異なるブランチのため、そのままマージすることはできません。そこで git rebase --onto
を利用します。
git rebase --onto {a} {b}
は a
のコミットの上に b
のコミットから先のコミットを積み上げた状態を作り出します。
ここでは先ほど git commit-tree
で作成したコミット f9ba938
の上に、そのコミットのもとになった 02c4027
(a-5)を積み上げます。
~/work/tmp/git-repo merge-to-mirror-1
$ git rebase --onto f9ba938 02c4027 --committer-date-is-author-date
Successfully rebased and updated refs/heads/merge-to-mirror-1.
これでmasterに積まれた2つのコミット b-1 b-2 が f9ba938
の上に積まれました。(masterとはparentが異なるため、2つのコミットのコミットハッシュは変わり、内容を同一にする新たな歴史となっています。)
~/work/tmp/git-repo merge-to-mirror-1
$ git log --oneline
147c0b7 (HEAD -> merge-to-mirror-1) b-2
1673726 b-1
f9ba938 (mirror) init
このブランチは f9ba938
が共通祖先となるためmirrorにマージが可能です。
~/work/tmp/git-repo mirror
$ git merge merge-to-mirror-1
Updating f9ba938..147c0b7
Fast-forward
hoge.txt | 1 +
sample.txt | 1 +
2 files changed, 2 insertions(+)
create mode 100644 hoge.txt
create mode 100644 sample.txt
~/work/tmp/git-repo mirror
$ git log --oneline
147c0b7 (HEAD -> mirror, merge-to-mirror-1) b-2
1673726 b-1
f9ba938 init
これでmasterに積まれたコミットと同じ(内容の)コミットをmirrorに同期することができました。
さらに追加されたコミットを同期する
ここまでで本記事の説明はおよそ完了していますが、最後に、masterに積まれたコミットを同期し続けるために重要なことを説明します。
ひとまずmasterに更に2つのコミット c-1
c-2
を追加して解説を進めます。
~/work/tmp/git-repo master
$ git log --oneline
e80b38e (HEAD -> master) c-2
9c42817 c-1
6c5b4d7 b-2
0397b61 b-1
02c4027 a-5
95f6a9f a-4
91439fb a-3
14bbaa5 a-2
92c187d a-1
そして先程と同じように git rebase --onto
を利用して、f9ba938
の上にコミットを積み替えます。
ここで、説明のために、先程はrebaseに指定してした --committer-date-is-author-date
を外して実行します。
~/work/tmp/git-repo merge-to-mirror-2-test
$ git rebase --onto f9ba938 02c4027
Successfully rebased and updated refs/heads/merge-to-mirror-2-test.
このブランチのコミットを見てみるとどうでしょうか、先程mirrorに取り込み済みのb-1とb-2のコミットハッシュが変更されてしまいました。
a703e4b (HEAD -> merge-to-mirror-2-test) c-2
bd55655 c-1
e9f0a01 b-2 # 先ほどは 147c0b7 だった
cc9d156 b-1 # 先ほどは 1673726 だった
f9ba938 init
ここで原因調査のために、master、mirror、merge-to-mirror-2-testそれぞれのb-1コミットの中身を確認してみましょう。
master
の b-1 (オリジナル)
$ git cat-file -p 0397b61
tree 0f114833b7e9fe060ebd024e464033024992f6d3
parent 02c4027167ca4f1a876eb897ecadad0a6af304c6
author genta <****@gmail.com> 1752892544 +0900
committer genta <****@gmail.com> 1752892544 +0900
b-1
mirror
の b-1
parentはオリジナル異なりますが、それ以外は同じです。
$ git cat-file -p 1673726
tree 0f114833b7e9fe060ebd024e464033024992f6d3
parent f9ba9385fa4d03f0d9b97fdf172bab077e9b9a9f
author genta <****@gmail.com> 1752892544 +0900
committer genta <****@gmail.com> 1752892544 +0900
b-1
merge-to-mirror-2-test
の b-1
mirrorのb-1とparentは等しいですが、committerのdateが変更されてしまっています。
$ git cat-file -p cc9d156
tree 0f114833b7e9fe060ebd024e464033024992f6d3
parent f9ba9385fa4d03f0d9b97fdf172bab077e9b9a9f
author genta <****@gmail.com> 1752892544 +0900
committer genta <****@gmail.com> 1752895604 +0900
b-1
どうやら、committerのdateが変更されてしまっているために同じコミットでもコミットハッシュが変わってしまっているようです。
git rebase
のデフォルトの挙動は、committerのdateをrebaseした日付に書き換えてしまうため、このような問題が発生します。
こちらを回避するために git rebase --onto
で --committer-date-is-author-date
を指定することでauthorのdateとcommitterのdateを揃えることができ、複数回rebaseを行ってもparentが同一であればコミットハッシュを維持することができます。
では、改めてmasterの内容をmirrorに取り込み直しましょう。
~/work/tmp/git-repo merge-to-mirror-2
# --committer-date-is-author-date を指定する!
$ git rebase --onto f9ba938 02c4027 --committer-date-is-author-date
Successfully rebased and updated refs/heads/merge-to-mirror-2.
~/work/tmp/git-repo merge-to-mirror-2
$ git --no-pager log
cfacc5d (HEAD -> merge-to-mirror-2) c-2
b128de2 c-1
147c0b7 (mirror, merge-to-mirror-1) b-2
1673726 b-1
f9ba938 init
どうでしょうか、b-1、b-2のコミットハッシュが維持されていますね。
これであれば、このままmerge-to-mirror-2をmirrorにマージすることができます。
~/work/tmp/git-repo mirror
$ git merge merge-to-mirror-2
Updating 147c0b7..cfacc5d
Fast-forward
README.md | 1 +
sample.txt | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
create mode 100644 README.md
~/work/tmp/git-repo mirror
$ git log --oneline
cfacc5d (HEAD -> mirror, merge-to-mirror-2) c-2
b128de2 c-1
147c0b7 (merge-to-mirror-1) b-2
1673726 b-1
f9ba938 init
以降はmasterが進む度に、masterブランチに git rebase --onto f9ba938 02c4027 --committer-date-is-author-date
を実行したブランチを作成することで、mirrorにマージするブランチを作り続けることができます。
これで本流のブランチ(master)から、ある地点以前の歴史を一つのコミットにまとめた新たな歴史のブランチ(mirror)に、元のブランチの変更を同期し続けることが実現できます。
参考記事
https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%84%E3%83%BC%E3%83%AB-Git-%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88
https://git-scm.com/docs/git-commit-tree
https://www.abap34.com/posts/git_date.html