為什麼我愛用"git rebase"

繁體中文 | English

# 背景

相信當大部分人剛接觸 Git 的人要合併 branch 時,一率都用 git merge。但是當我了解 rebase 後,一整個愛不釋手。

阿我懶得講最基本指令用法,如果你重來沒用過rebase請服用 (opens new window)

# 為什麼我愛用git rebase

# 專案的歷史非常整潔直觀

mergerebase都可以合併分支,那來看看他們的差別:

merge會把兩個分支的 commits 一樣照時間排序摻在一塊,合併為一個分支,就像七龍珠合體,合體完都摻在一起了: Dragon Ball

rebase則是重定基底,兩個分支的 commits 時間我不管,但是同個分支內的 commits 都放在一起,就像海賊王合體一樣,就算合體完成我還是一看就知道是由什麼組成,因為來自哪裡都擺在一塊 👍: One Piece

# 解決衝突的方式

merge會產生一個 merge commit,沒有衝突的情況可以 fast-forward (opens new window),但遇到衝突,就一定得要提交一個 commit 來解決衝突。

rebase不會產生任何多餘的 commit 👍。

rebase實際上像是重定完基底後,把 commit 一個個重新提交,而遇到衝突就停下讓你解決,這樣的解衝突方法我個人滿喜歡的,一步一步的解思緒比較清楚,方便釐清當前正在做什麼,要留什麼,不留什麼。但是也有壞處,當衝突過多,要解很久,很繁瑣。

Show me the diagram:

  • 有個 Master branch,兩條基於 master 的分支,feat-afeat-b 同時進行開發

Git flow diagram - init

  • Merge feat-a 後,要 merge feat-b 發現有 conflicts

Git flow diagram - feat a merged

  • 用 merge 解決完 conflicts,很可能會變成以下狀況,都摻在一起了,而且多了一個 merge commit

Git flow diagram - feat b merged

  • 但如果用 rebase 解決完 conflicts,會留下直觀的紀錄

Git flow diagram - feat b rebased

至於有什麼好處,不覺得摻在一起很難看嗎?如果你不覺得,reviewer 很可能會覺得,透過後者能夠清楚知道哪些 commits 是這個 branch (PR) 的影響範圍。如果是摻在一起,就無法從各個 commit 來進行 review。

# 自由修改所有 commits

這就跟合併分支無關,上面提到rebase把 commit 一個個重新提交,那既然可以重新提交,當然也可重新修改 commit 內容吧:

git rebase -i (opens new window)

大概會顯示以下這種畫面:

pick 30e43f8 docs: update Readme
pick 4103b7e feat: add Z feature
pick b962eeb feat: add A feature
pick f296540 feat: add B feature
pick a49b581 feat: add D feature
pick 0967d97 feat: add E feature

# Rebase dc2a5e0..0967d97 onto dc2a5e0 (6 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.

你可以重新排列 commit 順序、修改 commit message、捨棄 commit 甚至融合 commit 等等。 我個人覺得這超好用。

# 小夥伴 git push --force-with-lease

# 作用

rebase 非常好用但是有它的副作用,rebase 代表修改了歷史,如果只在本地沒有影響,但如果有推到遠端分支,這代表你得git push -f,但全世界都知道git push -f多危險。

所以我只用git push --force-with-lease,它會去檢查遠端分支是否有其他人做新的提交,如果不如它預期就會拒絕你 push。

This option allows you to say that you expect the history you are updating is what you rebased and want to replace. If the remote ref still points at the commit you specified, you can be sure that no other people did anything to the ref.

被拒絕如下:

git push origin master --force-with-lease
To github.com:newsbielt703/test-git-push--force-with-lease.git
 ! [rejected]        master -> master (stale info)
error: failed to push some refs to 'git@github.com:newsbielt703/test-git-push--force-with-lease.git'

# 出事了怎麼辦

如果你搞砸了,傻傻地用了git push -f出事了怎麼辦,有個好用的東西git-blame-someone-else (opens new window),安裝完後,照著 Readme 指示執行git blame-someone-else "yourteammate <yourteamate@gmail.com>" <commit>,再看看git log,不是你的錯了:

commit 70f45487814217d0226f7eae8d0caa0734775353 (HEAD -> master, origin/master)
Author: yourteammate <yourteammate@gmail.com>
Date:   Thu Aug 15 20:57:00 2019 +0800

    feat: add E feature

commit a202ae4447adebe5bfe3e73e678665a8bfdf6f0f
Author: yourteammate <yourteammate@gmail.com>
Date:   Thu Aug 15 20:56:43 2019 +0800

    feat: add D feature

commit be908366c98052077d893dedc28baf92dffacb71
Author: yourteammate <yourteammate@gmail.com>
Date:   Thu Aug 15 20:41:02 2019 +0800

    feat: add B feature

commit 10e6a57d3061abc55798f815a790bba6307039e5
Author: yourteammate <yourteammate@gmail.com>
Date:   Thu Aug 15 20:40:47 2019 +0800

    feat: add A feature

commit 4103b7ed93993635b2b7ac35ec2ceab79a7d6446
Author: Billyyyyy3320 <newsbielt703@gmail.com>
Date:   Thu Aug 15 20:52:04 2019 +0800

    feat: add Z feature

commit 30e43f858ef692b26380cceda5a84ac8a6c6e3d5
Author: Billyyyyy3320 <newsbielt703@gmail.com>
Date:   Thu Aug 15 20:47:59 2019 +0800

    docs: update Readme

commit dc2a5e0c60991925dd7fa4858ff367534000b380
Author: Billyyyyy3320 <newsbielt703@gmail.com>
Date:   Thu Aug 15 20:37:19 2019 +0800

    feat: init

這段是玩笑,別真的用啊!
這段是玩笑,別真的用啊!
這段是玩笑,別真的用啊!

This changes not only who authored the commit but the listed commiter as well. It also is something I wrote as a joke, so please don't run this against your production repo and complain if this script deletes everything.


End.