Pixel Pedals of Tomakomai

北海道苫小牧市出身の初老の日常

git log --first-parentのすゝめ

かなり長い間 git svn使わざるを得なかった愛用していたこともあって、個人的に git log --first-parent が大好きなんだけど、ふとググったらHamanoさん自らが素晴らしいエントリを書いてたので紹介。

Depending on the work style of their project, sometimes people wonder what story git log --first-parent output is trying to tell, and this article is about demystifying it.

Fun with --first-parent

要は git log --first-parent するとトピックブランチでの細かい修正をサマリ的に表示して、プロジェクトの変更の流れを俯瞰できて良いって話なのだけど、この仕組みを機能させたければ、mergeの時にfirst parentを意識したマージを行う必要がある。


以下は--first-parentが機能する例。

【コミットを作成】
% git init
Initialized empty Git repository in sample1/.git/
% echo "Initial line" > initial.txt
% git add initial.txt; git commit -am"initial.txt"
[master (root-commit) fc1e970] initial.txt
 1 file changed, 1 insertion(+)
 create mode 100644 initial.txt

【トピックに切り替え、2回コミット】
% git checkout -b topic
Switched to a new branch 'topic'
% echo "{ start topic branch ..." >> initial.txt
% git commit -am"Start implementing topic"
[topic fd2941e] Start implementing topic
 1 file changed, 1 insertion(+)
% echo "... the end of topic branch }" >> initial.txt
% git commit -am"Finish implementing topic"
[topic edf34c4] Finish implementing topic
 1 file changed, 1 insertion(+)

【マスタに戻り、1つコミットを追加】
% git checkout master
Switched to branch 'master'
% echo "Second file" > second.txt
% git add second.txt; git commit -am"Add second.txt"
[master d6e1162] Add second.txt
 1 file changed, 1 insertion(+)
 create mode 100644 second.txt

【マスタからトピックをマージ】
% git merge --no-ff topic
Merge made by the 'recursive' strategy.
 initial.txt |    2 ++
 1 file changed, 2 insertions(+)

【トピックでの作業が、1コミットとして表示される】
% git log --first-parent
commit 0f623aeca3a446e4ddf7bdb1a1174ffd8d2b7186
Merge: d6e1162 edf34c4
Author: Masahiro Honma <hir...@cpan.org>
Date:   Wed Nov 7 19:05:32 2012 +0900

    Merge branch 'topic'

commit d6e11624e9586d39b4149ec0f98c0904ac10bd12
Author: Masahiro Honma <hir...@cpan.org>
Date:   Wed Nov 7 19:05:22 2012 +0900

    Add second.txt

commit fc1e97092e40f0714f3d3f2060dfa7b00eda0517
Author: Masahiro Honma <hir...@cpan.org>
Date:   Wed Nov 7 18:54:11 2012 +0900

    initial.txt

ところが、マスタに戻らずにトピックからmergeコマンドを叩くと、--first-parentの恩恵が受けられなくなる。

【トピックからマスタをマージ】
% git branch
  master
* topic
% git merge --no-ff master
Merge made by the 'recursive' strategy.
 second.txt |    1 +
 1 file changed, 1 insertion(+)
 create mode 100644 second.txt

【マスタへ移り、マージ済のトピックへfast-forward】
% git checkout master
Switched to branch 'master'
% git merge --ff-only topic
Updating d6e1162..5f7da9d
Fast-forward
 initial.txt |    2 ++
 1 file changed, 2 insertions(+)

【マスタの履歴が隠され、トピックの履歴が公に出てしまっている】
% git log --first-parent
commit 5f7da9dc1f23b421a2fdcac7ad44d59c83af4287
Merge: edf34c4 d6e1162
Author: Masahiro Honma <hir...@cpan.org>
Date:   Wed Nov 7 19:08:59 2012 +0900

    Merge branch 'master' into topic

commit edf34c4de114fa1af842e80655de698aaec8baa4
Author: Masahiro Honma <hir...@cpan.org>
Date:   Wed Nov 7 18:56:16 2012 +0900

    Finish implementing topic

commit fd2941e029da767d79924c1020ef800583405afe
Author: Masahiro Honma <hir...@cpan.org>
Date:   Wed Nov 7 18:55:48 2012 +0900

    Start implementing topic

commit fc1e97092e40f0714f3d3f2060dfa7b00eda0517
Author: Masahiro Honma <hir...@cpan.org>
Date:   Wed Nov 7 18:54:11 2012 +0900

    initial.txt

基本的には、「mergeを叩くときはmaster(により近い)ブランチから」を心がければ大丈夫。だけど、Hamanoさんのエントリでも最後に取り上げられているように、pullをするときは注意。masterとorigin/masterでは後者の方がmasterに近いと考えられるため、master側にマージコミットができるpull操作は--first-parentの恩恵を壊してしまう。

この件に関するHamanoさんの見解は以下に引用する通り。

It is a judgement call if this "purist" approach to avoid the "last minite" first-parent breakage is worth it. I personally do not think it is, but others may disagree.

Junio C Hamano

自分は考え方は"purist"に近いけど、このパターンだと状況によっては(例えば、愚直に切り戻すとテストが大変とかデグレのリスクが大きいとき)次のようにするかも。

【HEADはマスタという前提。masterを別名に退避】
$ git branch conflicted-master master

【masterをorigin/masterの状態にする】
$ git fetch origin  ← pullの代わり(普段からpull使わない派)
$ git reset --hard origin/master

【masterへpushできなかったmaster(M)を再度マージ】
$ git merge conflicted-master

【「conflicted-master」って名前を表に出したくなければコミットを編集】
$ git commit --amend
--編集--

【テストし、masterをpush。競合したら最初に戻る・・・】
$ test
$ git push