clock-up-blog

go-mi-tech

[Git]rebase不要のコミット挿入(マージによる疑似挿入)

一般的に Git リポジトリをチーム運用している場合、master ブランチの履歴を書き換えるために rebase するようなことはできない(手元で rebase はできても push -f が禁止されている)ことが普通かと思います。

このような rebase の難しいブランチに対して「マージ」操作のみで過去履歴を挿入する(挿入されたように見せる)手法を紹介します。

一般的なマージ操作のイメージ

一般的に「ブランチ作成」「コミット作成」「マージ」のフローを実行したとき、以下のような履歴変化のイメージが考えられます。

しかし実際には図内の右のような一直線の構造の実体が作られるわけではなく、一直線の表示はあくまでも疑似的な表現にすぎないことに留意してください。ブランチによって生じた分岐構造は rebase 等をしない限りは残り続けます。

GitHub 表示 (一直線に見える)
f:id:kobake:20171109082034p:plain:w300

git log 表示 (一直線に見える)
f:id:kobake:20171109082147p:plain:w300

SourceTree 表示 (分岐構造まで見える)
f:id:kobake:20171109092926p:plain:w400

マージされたコミットの表示順序

マージされたコミット群の表示位置は、必ずしもマージ先(今回の例では master)の最新位置に表示されるわけではなく、実際にはコミット同士の CommitDate の値が比較され、それらが降順になるような順序で表示されます。

つまり、コミット X の CommitDate を操作すればその挿入位置(挿入されているように見える位置)をずらすことができます。

CommitDate について

Git のコミットに記録される日時には AuthorDate と CommitDate の2種類があります。
参考:Git のコミットのタイムスタンプには author date と committer date の 2 種類があるという話 - ひだまりソケットは壊れない

  • AuthorDate … コミット作成日時
  • CommitDate … コミット変更日時 (rebase 等で変わる)

git log や GitHub 等における一般的なコミット日時の表示には AuthorDate が用いられるため、一方の CommitDate を目にすることはあまり多くないと思います。CommitDate は「git log --pretty=fuller」のようにオプション指定のログで確認することができます。

$ git log --pretty=fuller
commit f920be10fc03037fa5f67b41b9389df840a48524 (HEAD -> master, origin/master)
Author:     kobake <kobake@users.sourceforge.net>
AuthorDate: 2017-11-09 07:30:00 +0900
Commit:     kobake <kobake@users.sourceforge.net>
CommitDate: 2017-11-09 07:30:00 +0900 ★ココを確認

    D

commit 0b05fb2788254be10bdf0ddc2d75b46acd54cb38
Author:     kobake <kobake@users.sourceforge.net>
AuthorDate: 2017-11-09 07:20:00 +0900
Commit:     kobake <kobake@users.sourceforge.net>
CommitDate: 2017-11-09 07:20:00 +0900 ★ココを確認

    C

commit df4d7412ba06769c64327a7372ad8859f41f8da3
Author:     kobake <kobake@users.sourceforge.net>
AuthorDate: 2017-11-09 07:10:00 +0900
Commit:     kobake <kobake@users.sourceforge.net>
CommitDate: 2017-11-09 07:10:00 +0900 ★ココを確認

    B

コミットB, C, D の CommitDate はそれぞれ「2017-11-09 07:10:00 +0900」「2017-11-09 07:20:00 +0900」「2017-11-09 07:30:00 +0900」であることが分かりました。


CommitDate はコミット操作時点の日時が自動的に記録されるものですが、環境変数 GIT_COMMITTER_DATE により任意日時を指定することもできます。

$ touch t.txt
$ git add t.txt
$ GIT_COMMITTER_DATE="2017-11-09 07:15:00 +0900" git commit -m "test"
$ git log -1 --pretty=fuller
commit 2eb0e7025c73cb16b1156c30e5982303159d5bbb (HEAD -> master)
Author:     kobake <kobake@users.sourceforge.net>
AuthorDate: 2017-11-09 09:40:43 +0900
Commit:     kobake <kobake@users.sourceforge.net>
CommitDate: 2017-11-09 07:15:00 +0900 ★「GIT_COMMITTER_DATE」で指定した値になった

    test

時系列を操作してコミット位置をずらす

上述の性質を利用して、コミットを任意位置に挿入する(挿入したように見せる)ことができます。

今回は master に積まれているコミット A, B, C, D の中で、B と C の間に新しいコミット X を挿入してみます。


■ master の現在状態を確認
$ git checkout master
$ git log --oneline
f920be1 (HEAD -> master) D
0b05fb2 C
df4d741 B
a03406a A
....

■ B の位置からブランチ「branch」を作る
$ git checkout -b branch df4d741

■ (1) B, C の CommitDate は 07:10, 07:20 であることが分かっている(前項で確認)ので、
   新しいコミット X の CommitDate はその間の 07:15 とする
$ echo xxx > x.txt
$ git add x.txt
$ GIT_COMMITTER_DATE="2017-11-09 07:15:00 +0900" git commit -m "X"

■ (2) master に対して「branch」をマージ
$ git checkout master
$ git merge branch --no-edit

■ (3) 結果確認
$ git push origin master
$ git log --oneline
6937261 (HEAD -> master, origin/master) Merge branch 'branch'
f920be1 D
0b05fb2 C
c0d1b36 (branch) X  … X が B と C の間に挿入された (ように見える)
df4d741 B
a03406a A
....


結果:GitHub 表示
f:id:kobake:20171109101135p:plain:w250

結果:SourceTree 表示
f:id:kobake:20171109101400p:plain:w350

マージ操作により、B と C の間に新しいコミット X が挿入されました(挿入されたように見せることができました)。

おまけの情報

  • CommitDate を手動でいじるのは「お作法」として「かなりよろしくない」部類にあたる気はする。あまり濫用しないこと。
  • 「git log --graph」コマンドでも分岐情報の表示はできるが、時系列が反映された順序にはならない (Git 2.14.3 時点) ので、本記事では紹介を省略し、SourceTree での分岐表示の例を載せた(こちらは時系列が反映された表示となる)。
  • 今回は CommitDate のみ操作したが、AuthorDate (一般的によく表示される日時) も「commit --date="YYYY-MM-DD hh:mm:ss +ZZZZ"」で指定できるので、これも必要があれば指定する。
  • SourceTree のコミット一覧に表示される日時は AuthorDate ではなく CommitDate がデフォルトとなっている(オプションで変更可)。
  • ブランチ分岐元よりもさらに古い CommitDate を指定したコミットを作成すると、マージ時にツリー構造がめちゃくちゃになるので絶対にやめたほうが良い。と思う。
  • ましてや initial commit よりも古い CommitDate のコミットをマージすると以下みたいなことになる。あかん。

f:id:kobake:20171109111541p:plain:w600

f:id:kobake:20171109111547p:plain:w400

ご利用は計画的に。慎重に。濫用ダメ絶対。

});