如果你不能很好的應(yīng)用 Git,那么這里為你提供一個(gè)非常棒的 Git 在線練習(xí)工具 Git Online ,你可以更直觀的看到你所使用的命令會(huì)產(chǎn)生什么效果
另外,你在使用 Git 合并分支時(shí)只會(huì)使用 git merge
嗎?有時(shí)使用 git rebase
可以比 git merge
做出更優(yōu)雅的操作
創(chuàng)新互聯(lián)公司IDC提供業(yè)務(wù):棕樹數(shù)據(jù)中心,成都服務(wù)器租用,棕樹數(shù)據(jù)中心,重慶服務(wù)器租用等四川省內(nèi)主機(jī)托管與主機(jī)租用業(yè)務(wù);數(shù)據(jù)中心含:雙線機(jī)房,BGP機(jī)房,電信機(jī)房,移動(dòng)機(jī)房,聯(lián)通機(jī)房。
不知怎么,git rebase
命令被賦予了一個(gè)神奇的污毒聲譽(yù),初學(xué)者應(yīng)該遠(yuǎn)離它,但它實(shí)際上可以讓開發(fā)團(tuán)隊(duì)在使用時(shí)更加輕松。
你可以將它理解成「七傷拳」,「七傷拳」并不是不能練,只是練「七傷拳」有一個(gè)先訣條件,那就是內(nèi)功境界一定要非常高,即你要充分理解
git rebase
命令
在本文中,我們將 git rebase
與 git merge
命令進(jìn)行比較。在 Git 工作流中,說(shuō)明所有可以使用 rebase 的場(chǎng)景
關(guān)于 git rebase
,首先要理解的是它解決了和 git merge
同樣的問(wèn)題。這兩個(gè)命令都旨在將更改從一個(gè)分支合并到另一個(gè)分支,但二者的合并方式卻有很大的不同。
當(dāng)你在專用分支上開發(fā)新 feature 時(shí),然后另一個(gè)團(tuán)隊(duì)成員在 master
分支提交了新的 commits,這會(huì)發(fā)生什么?這會(huì)導(dǎo)致分叉的歷史記錄,對(duì)于這個(gè)問(wèn)題,使用 Git 作為協(xié)作工具的任何人來(lái)說(shuō)都應(yīng)該很熟悉。
現(xiàn)在,假設(shè)在 master
分支上的新提交與你正在開發(fā)的 feature 相關(guān)。需要將新提×××并到你的 feature
分支中,你可以有兩個(gè)選擇:merge 或者 rebase。
最簡(jiǎn)單的方式是通過(guò)以下命令將 master
分支合并到 feature
分支中:
git checkout feature
git merge master
或者,你可以將其濃縮為一行命令:
git merge feature master
這會(huì)在 feature
分支中創(chuàng)建一個(gè)新的 merge commit,它將兩個(gè)分支的歷史聯(lián)系在一起,請(qǐng)看如下所示的分支結(jié)構(gòu):
使用 merge 是很好的方式,因?yàn)樗且环N 非破壞性的操作?,F(xiàn)有分支不會(huì)以任何方式被更改。這避免了 rebase 操作所產(chǎn)生的潛在缺陷(下面討論)。
另一方面,這也意味著 feature
分支每次需要合并上游更改時(shí),它都將產(chǎn)生一個(gè)額外的合并提交。如果master
提交非常活躍,這可能會(huì)嚴(yán)重污染你的 feature 分支歷史記錄。盡管可以使用高級(jí)選項(xiàng) git log
緩解此問(wèn)題,但它可能使其他開發(fā)人員難以理解項(xiàng)目的歷史記錄
作為 merge 的替代方法,你可以使用以下命令將 master
分支合并到 feature
分支上:
git checkout feature
git rebase master
這會(huì)將整個(gè) feature
分支移動(dòng)到 master
分支的頂端,從而有效地整合了所有 master
分支上的提交。但是,與 merge 提交方式不同,rebase 通過(guò)為原始分支中的每個(gè)提交創(chuàng)建全新的 commits 來(lái) 重寫項(xiàng)目歷史記錄。
rebase 的主要好處是可以獲得更清晰的項(xiàng)目歷史。首先,它消除了 git merge
所需的不必要的合并提交;其次,正如你在上圖中所看到的,rebase 會(huì)產(chǎn)生完美線性的項(xiàng)目歷史記錄,你可以在 feature
分支上沒有任何分叉的情況下一直追尋到項(xiàng)目的初始提交。這樣可以通過(guò)命令 git log
,git bisect
和 gitk
更容易導(dǎo)航查看項(xiàng)目。
但是,針對(duì)這樣的提交歷史我們需要權(quán)衡其「安全性」和「可追溯性」。如果你不遵循 [Rebase 的黃金法則](## Rebase 的黃金法則),重寫項(xiàng)目歷史記錄可能會(huì)對(duì)你的協(xié)作工作流程造成災(zāi)難性后果。而且,rebase 會(huì)丟失合并提交的上下文, 你也就無(wú)法看到上游更改是何時(shí)合并到 feature 中的。
交互式 rebase 使你有機(jī)會(huì)在將 commits 移動(dòng)到新分支時(shí)更改這些 commits。這比自動(dòng) rebase 更強(qiáng)大,因?yàn)樗峁┝藢?duì)分支提交歷史的完全控制。通常,這用于在合并 feature 分支到 master
之前清理其雜亂的歷史記錄。
要使用交互式 rebase,需要使用 git rebase
和 -i
選項(xiàng):
git checkout feature
git rebase -i master
這將打開一個(gè)文本編輯器,列出即將移動(dòng)的所有提交:
pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
此列表準(zhǔn)確定義了執(zhí)行 rebase 后分支的外觀。通過(guò)更改 pick
命令或重新排序條目,你可以使分支的歷史記錄看起來(lái)像你想要的任何內(nèi)容。例如,如果第二次提交 fix 了第一次提交中的一個(gè)小問(wèn)題,您可以使用以下 fixup
命令將它們濃縮為一個(gè)提交:
pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
保存并關(guān)閉文件時(shí),Git將根據(jù)您的指示執(zhí)行 rebase,從而產(chǎn)生如下所示的項(xiàng)目歷史記錄:
消除這種無(wú)意義的提交使你的功能歷史更容易理解。這是 git merge
根本無(wú)法做到的事情。至于 commits 條目前的 pick
、fixup
、squash
等命令,在 git 目錄執(zhí)行 git rebase -i
即可查看到,大家按需重排或合并提交即可,注釋說(shuō)明非常清晰,在此不做過(guò)多說(shuō)明:
一旦你理解了什么是 rebase,最重要的是要學(xué)習(xí)什么時(shí)候不能使用它。git rebase
的黃金法則是永遠(yuǎn)不要在公共分支上使用它。
例如,想想如果你 rebase master
分支到 feature
分支之上會(huì)發(fā)生什么:
rebase 將所有 master
分支上的提交移動(dòng) feature
分支的頂端。問(wèn)題是這只發(fā)生在 你自己的存儲(chǔ)庫(kù)中。所有其他開發(fā)人員仍在使用原始版本的 master
。由于 rebase 導(dǎo)致全新 commit,Git 會(huì)認(rèn)為你的 master
分支歷史與其他人的歷史不同。
此時(shí),同步兩個(gè) master
分支的唯一方法是將它們合并在一起,但是這樣會(huì)產(chǎn)生額外的合并提交和兩組包含相同更改的提交(原始提交和通過(guò) rebase 更改的分支提交)。不用說(shuō),這是一個(gè)令人非常困惑的情況。
因此,在你運(yùn)行 git rebase
命令之前,總是問(wèn)自己,還有其他人在用這個(gè)分支嗎?如果答案是肯定的,那就把你的手從鍵盤上移開,開始考慮采用非破壞性的方式進(jìn)行改變(例如,git revert
命令)。否則,你可以隨心所欲地重寫歷史記錄。
如果你嘗試將 rebase 了的 master
分支推送回 remote repository,Git 將阻止你這樣做,因?yàn)樗鼤?huì)與遠(yuǎn)程master
分支沖突。但是,你可以通過(guò)傳遞 --force
標(biāo)志來(lái)強(qiáng)制推送,如下所示:
# Be very careful with this command!
git push --force
這樣你自己 repository 的內(nèi)容將覆蓋遠(yuǎn)程 master
分支的內(nèi)容,但這會(huì)使團(tuán)隊(duì)的其他成員感到困惑。因此,只有在確切知道自己在做什么時(shí)才要非常小心地使用此命令。
如果沒有人在 feature branch 上作出更改,你可以使用 force push 將本地內(nèi)容推送到 remote repository 做清理工作
rebase 可以根據(jù)你所在團(tuán)隊(duì)的需要方便的整合到現(xiàn)有的 Git 工作流程中。在本節(jié)中,我們將了解 rebase 在功能開發(fā)的各個(gè)階段可以提供的好處。
在任何工作流程中,利用 git rebase
是為每個(gè)功能創(chuàng)建專用分支。這為你提供了必要的分支,以安全地利用 rebase:
將 rebase 納入工作流程的最佳方法之一是清理本地正在進(jìn)行的功能。通過(guò)定期執(zhí)行交互式 rebase,你可以確保功能中的每個(gè)提交都具有針對(duì)性和意義。這可以使你在編寫代碼時(shí)無(wú)需擔(dān)心將其分解為隔離的提交(多個(gè)提交),你可以在事后修復(fù)整合它。
使用 git rebase
時(shí),有兩種情況:feature 父分支(例如 master
)的提交,或在 feature 中的早期提交。我們?cè)?交互式 Rebase部分已經(jīng)介紹了第一種情況的示例。我們來(lái)看后一種情況,當(dāng)你只需要修復(fù)最后幾次提交時(shí),以下命令僅做最后 3 次提交的交互式 rebase。
git checkout feature
git rebase -i HEAD~3
通過(guò)指定 HEAD~3
,你實(shí)際上并沒有移動(dòng)分支,你只是交互式地重寫其后的3個(gè)提交。請(qǐng)注意,這不會(huì)將上游更改合并到 feature
分支中。
如果要使用此方法重寫整個(gè)功能,git merge-base
命令可用于查找 feature
分支的原始 base。以下內(nèi)容返回原始 base 的提交ID,然后你可以將其傳遞給 git rebase
:
git merge-base feature master
交互式 rebase 的使用是引入git rebase
工作流的好方法,因?yàn)樗挥绊懕镜胤种АF渌_發(fā)人員唯一能看到的就是你提交的最終版,這應(yīng)該是一個(gè)簡(jiǎn)潔易懂易跟蹤的分支歷史記錄。
但同樣,這僅適用于 私有feature分支。如果你通過(guò)相同的功能分支(公共分支)與其他開發(fā)人員協(xié)作,那么你是 不被允許重寫其歷史記錄的。
在 概念概述部分中,我們了解了 feature 分支可以使用 git merge
或 git rebase
合并 master 分支的上游更改 。merge 是一個(gè)安全的方式,可以保留存 git repository 的整個(gè)歷史記錄,而 rebase 則是通過(guò)將 feature 分支移動(dòng)到 master
頂端來(lái)創(chuàng)建線性歷史記錄。
這種使用 git rebase
類似于本地清理,但在此過(guò)程中它包含了那些來(lái)自 master
上游提交。
請(qǐng)記住,將當(dāng)前提交 rebase 到遠(yuǎn)程 branch(非 master 分支)一樣是合法的。當(dāng)與另一個(gè)開發(fā)人員協(xié)作使用相同的功能并且你需要將他們的更改合并到你的 repository 時(shí),就會(huì)發(fā)生這種情況。
例如,如果你和另一個(gè)名為 John 的開發(fā)人員添加了對(duì) feature
分支的提交,在你 fetch (注意 fetch 并不會(huì)自動(dòng) merge )來(lái)自 John 的遠(yuǎn)程 feature
分支后,你的 repository 可能如下所示:
你可以整合上來(lái)自上游的分叉:要么用 john/feature
merge本地 feature
,要么 rebase本地feature
到john/feature
的頂部。
請(qǐng)注意,此 rebase 不違反 Rebase 黃金規(guī)則,因?yàn)橹挥心愕谋镜?feature
提交被移動(dòng), 之前的所有內(nèi)容都不會(huì)受到影響。這就像是說(shuō) "將我的更改添加到 John 已經(jīng)完成的工作中"。在大多數(shù)情況下,這比通過(guò)合并提交與遠(yuǎn)程分支同步更直觀。
默認(rèn)情況下,使用 git pull
命令執(zhí)行合并,但你可以通過(guò)向其傳遞 --rebase
選項(xiàng)來(lái)強(qiáng)制它將遠(yuǎn)程分支 以 rebase 方式集成。
git pull --rebase
如果你在代碼審查過(guò)程中使用 pull 請(qǐng)求,在使用了 pull 請(qǐng)求之后你應(yīng)該避免使用 git rebase
。一旦你發(fā)出 pull 請(qǐng)求,其他開發(fā)人員就會(huì)查看你的提交,這意味著它是一個(gè) 公共分支。重寫其歷史記錄將使 Git 和你的隊(duì)友無(wú)法跟蹤添加到該功能的任何后續(xù)提交。
其他開發(fā)人員的任何更改都需要合并 git merge
而不是 git rebase
。
因此,在提交拉取請(qǐng)求之前,通常使用交互式 rebase 清理代碼通常是個(gè)好的辦法。注意使用順序
在你的團(tuán)隊(duì)批準(zhǔn)某項(xiàng) feature 后,你可以選擇將該功能 rebase 到 master
分支的頂端,然后git merge
再將該功能集成到主代碼庫(kù)中。
這與將上游更改合并到 feature 分支中的情況類似,但由于你不允許在 master
分支中重寫提交,因此你必須最終使用 git merge
該功能進(jìn)行集成。但是,通過(guò)在 merge 之前執(zhí)行 rebase,你可以確保會(huì)以 fast-forward 方式 merge,從而產(chǎn)生完美的線性歷史記錄。
如果您不熟悉 git rebase
,可以隨時(shí)在臨時(shí)分支中執(zhí)行 rebase。這樣,如果你不小心弄亂了功能的歷史記錄,可以查看原始分支,然后重試。例如:
git checkout feature
git checkout -b temporary-branch
git rebase -i master
# [Clean up the history]
git checkout master
git merge temporary-branch
如果你更喜歡沒有不必要的干凈的合并提交,線性歷史記錄,你就需要開始了解使用 rebase 功能。同時(shí)你應(yīng)該會(huì)使用 git rebase
而不是 git merge
集成來(lái)自另一個(gè)分支的更改。
另一方面,如果你想保留項(xiàng)目的完整歷史記錄并避免重寫公共提交的風(fēng)險(xiǎn),你可以堅(jiān)持下去git merge
。這兩種選擇都是完全有效的,但至少現(xiàn)在你可以選擇利用 git rebase
的好處 。
git rebase
嗎?這樣清晰的線形歷史是不是可以嘗試一下呢?git pull -rebase
?帶著疑問(wèn)去思考,然后串聯(lián),進(jìn)而歸納總結(jié),不斷追問(wèn)自己,進(jìn)行自我辯證,像偵查嫌疑案件一樣看待技術(shù)問(wèn)題,漆黑的街道,你我一起尋找線索,你就是技術(shù)界大偵探福爾摩斯
輕松高效玩轉(zhuǎn)DTO(Data Transfer Object)
歡迎持續(xù)關(guān)注公眾號(hào):「日拱一兵」
- 前沿 Java 技術(shù)干貨分享
- 高效工具匯總
- 面試問(wèn)題分析與解答
- 技術(shù)資料領(lǐng)取
持續(xù)關(guān)注,帶你像讀偵探小說(shuō)一樣輕松趣味學(xué)習(xí) Java 技術(shù)棧相關(guān)知識(shí)