一言でいうと「git merge は、ファン目線=物まねで満足」「git rebase は、ストーカー目線=相手の家に押しかける」


これは何?



  • git mergegit rebase の違いを図で説明します。

  • 個人的に「commit を1つの箱と見立て、上に積み上げるイメージ」でも、うまく書けそうな気がしたので試してみました。

  • 結果、箱が積み重なった図が家(ビル)に見えてきました。

  • そうすると 『git merge は、ファン目線=物まねで満足」「git rebase は、ストーカー目線=相手の家に押しかける』 だなぁ。と感じ始めました。


リポジトリの初期状態


  • この状態から、git merge あるいは git rebase を行います。


    • 今現在、2つのブランチ origin/develop feature があります。

    • 現在位置(HEAD) は feature です。



1-original.png


git merge origin/develop (自分 feature に、相手 origin/develop の commit を積む)



  • git merge origin/develop すると次のようになります。

  • 自分 feature に、相手 origin/develop の全ての成果物(commit)を取り込みます。


    • 共通の祖先である A を基準とします。

    • 基準以降の相手の成果物である、C E が取込対象となります。

    • 取込対象は1つにまとめられ、C'E' として 自分に commit されます。

    • このように「相手の複数の成果物を1つにまとめて取り込む」のが、この場合の git merge の動きです。



2-git-merge.png


git rebase origin/develop (相手 origin/develop に、自分 feature の commit を積み直す)



  • git rebase origin/develop すると次のようになります。

  • 相手 origin/develop に、自分 feature の成果物(commit)を付け直します。


    • 共通の祖先である A を基準とします。

    • 基準以降の自分の成果物である B D が移動対象になります。

    • 移動対象は1commitずつ、相手に移動します。(正確には、移動ではなく、全く同じ内容でコピーが行われます。つまり、ハッシュ値が変わります)



  • ちなみに、B D は、git log 上からは見えなくなりますが、完全に削除されたわけではなく、git reflog HEAD で見えます。(詳細は、以下の参考を参照ください)

3-git-rebase.png


まとめ


  • 違いを簡潔に説明すると、、、


    • git merge は、自分に相手のcommitを積む。git rebase は、相手に自分のcommitを積み直す。」

    • git merge は、相手の家財道具と同じものを購入して自宅に置く。git rebase は、自宅の家財道具一式持って、相手の家に引っ越す」

    • git merge は、相手の物まねをする。git rebase は、自宅は引き払って相手の家に住む」

    • つまり「git merge は、ファン目線。git rebase は、ストーカー目線」




参考:上で説明したケースの実例


git merge の例


  • 実行コマンド

(

# set -x

mkdir try-git-merge
cd try-git-merge

git init

git config user.name "hoge"
git config user.email "hoge@example.com"

sleep 1
git checkout -b develop
echo "aaa" > a.txt
git add a.txt
git commit -m "add a.txt"

sleep 1
git checkout -b feature
echo "bbb" > b.txt
git add b.txt
git commit -m "add b.txt"

sleep 1
git checkout develop
echo "ccc" > c.txt
git add c.txt
git commit -m "add c.txt"

sleep 1
git checkout feature
echo "ddd" > d.txt
git add d.txt
git commit -m "add d.txt"

sleep 1
git checkout develop
echo "eee" > e.txt
git add e.txt
git commit -m "add e.txt"

git log --all --graph --oneline --date-order

sleep 1
git checkout feature
git merge --no-edit develop

git log --all --graph --oneline --date-order
git reflog HEAD
)


  • 実行結果

Initialized empty Git repository in /home/ubuntu/20190307/try-git-merge/.git/

Switched to a new branch 'develop'
[develop (root-commit) 9ce6cfc] add a.txt
1 file changed, 1 insertion(+)
create mode 100644 a.txt
Switched to a new branch 'feature'
[feature c6edd06] add b.txt
1 file changed, 1 insertion(+)
create mode 100644 b.txt![3-git-rebase.png](https://qiita-image-store.s3.amazonaws.com/0/275337/de3dcaaa-f3c8-ee43-cacb-cf5c778685c1.png)

Switched to branch 'develop'
[develop b0e661e] add c.txt
1 file changed, 1 insertion(+)
create mode 100644 c.txt
Switched to branch 'feature'
[feature 861542b] add d.txt
1 file changed, 1 insertion(+)
create mode 100644 d.txt
Switched to branch 'develop'
[develop 05140da] add e.txt
1 file changed, 1 insertion(+)
create mode 100644 e.txt
* 05140da (HEAD -> develop) add e.txt ・・・・ merge 前
| * 861542b (feature) add d.txt
* | b0e661e add c.txt
| * c6edd06 add b.txt
|/
* 9ce6cfc add a.txt
Switched to branch 'feature'
Merge made by the 'recursive' strategy.
c.txt | 1 +
e.txt | 1 +
2 files changed, 2 insertions(+)
create mode 100644 c.txt
create mode 100644 e.txt
* 35d2fc8 (HEAD -> feature) Merge branch 'develop' into feature ・・・ merge 後
|\
| * 05140da (develop) add e.txt
* | 861542b add d.txt
| * b0e661e add c.txt
* | c6edd06 add b.txt
|/
* 9ce6cfc add a.txt
35d2fc8 (HEAD -> feature) HEAD@{0}: merge develop: Merge made by the 'recursive' strategy.
861542b HEAD@{1}: checkout: moving from develop to feature
05140da (develop) HEAD@{2}: commit: add e.txt
b0e661e HEAD@{3}: checkout: moving from feature to develop
861542b HEAD@{4}: commit: add d.txt
c6edd06 HEAD@{5}: checkout: moving from develop to feature
b0e661e HEAD@{6}: commit: add c.txt
9ce6cfc HEAD@{7}: checkout: moving from feature to develop
c6edd06 HEAD@{8}: commit: add b.txt
9ce6cfc HEAD@{9}: checkout: moving from develop to feature
9ce6cfc HEAD@{10}: commit (initial): add a.txt


git rebase の例


  • 実行コマンド

(

#set -x

mkdir try-git-rebase
cd try-git-rebase

git init

git config user.name "hoge"
git config user.email "hoge@example.com"

sleep 1
git checkout -b develop
echo "aaa" > a.txt
git add a.txt
git commit -m "add a.txt"

sleep 1
git checkout -b feature
echo "bbb" > b.txt
git add b.txt
git commit -m "add b.txt"

sleep 1
git checkout develop
echo "ccc" > c.txt
git add c.txt
git commit -m "add c.txt"

sleep 1
git checkout feature
echo "ddd" > d.txt
git add d.txt
git commit -m "add d.txt"

sleep 1
git checkout develop
echo "eee" > e.txt
git add e.txt
git commit -m "add e.txt"

git log --all --graph --oneline --date-order

sleep 1
git checkout feature
git rebase develop

git log --all --graph --oneline --date-order
git reflog HEAD
)


  • 実行結果

Initialized empty Git repository in /home/ubuntu/20190307/try-git-rebase/.git/

Switched to a new branch 'develop'
[develop (root-commit) bf10f4f] add a.txt
1 file changed, 1 insertion(+)
create mode 100644 a.txt
Switched to a new branch 'feature'
[feature 532fb77] add b.txt
1 file changed, 1 insertion(+)
create mode 100644 b.txt
Switched to branch 'develop'
[develop fff94fd] add c.txt
1 file changed, 1 insertion(+)
create mode 100644 c.txt
Switched to branch 'feature'
[feature 5ef0acd] add d.txt
1 file changed, 1 insertion(+)
create mode 100644 d.txt
Switched to branch 'develop'
[develop 91dfce3] add e.txt
1 file changed, 1 insertion(+)
create mode 100644 e.txt
* 91dfce3 (HEAD -> develop) add e.txt ・・・ rebase 前
| * 5ef0acd (feature) add d.txt
* | fff94fd add c.txt
| * 532fb77 add b.txt
|/
* bf10f4f add a.txt
Switched to branch 'feature'
First, rewinding head to replay your work on top of it...
Applying: add b.txt
Applying: add d.txt
* f097caa (HEAD -> feature) add d.txt ・・・ rebase 後
* cda4d58 add b.txt
* 91dfce3 (develop) add e.txt
* fff94fd add c.txt
* bf10f4f add a.txt
f097caa (HEAD -> feature) HEAD@{0}: rebase finished: returning to refs/heads/feature
f097caa (HEAD -> feature) HEAD@{1}: rebase: add d.txt ・・・ D がコピーされ、rebase後 D' (f097caa) となった
cda4d58 HEAD@{2}: rebase: add b.txt ・・・・・・・・・・・・・ B がコピーされ、rebase後 B' (cda4d58) となった
91dfce3 (develop) HEAD@{3}: rebase: checkout develop
5ef0acd HEAD@{4}: checkout: moving from develop to feature
91dfce3 (develop) HEAD@{5}: commit: add e.txt
fff94fd HEAD@{6}: checkout: moving from feature to develop
5ef0acd HEAD@{7}: commit: add d.txt ・・・・・・・・・・・・・・ rebase後、使われなくなった D(5ef0acd)
532fb77 HEAD@{8}: checkout: moving from develop to feature
fff94fd HEAD@{9}: commit: add c.txt
bf10f4f HEAD@{10}: checkout: moving from feature to develop
532fb77 HEAD@{11}: commit: add b.txt ・・・・・・・・・・・・・ rebase後、使われなくなった B(532fb77)
bf10f4f HEAD@{12}: checkout: moving from develop to feature
bf10f4f HEAD@{13}: commit (initial): add a.txt