[英]Why do I need a merge after a rebase?
正如您在這里看到的( https://stackoverflow.com/a/9147389/16430630 ),我經常看到在變基后進行合並。 如果你看下面的圖片。 看起來 rebase 命令正在進行合並。 為什么需要合並兩次(rebase+merge)?
git checkout feature
git rebase master
git checkout master
git merge feature
如果我省略了最后一個命令git merge feature
會發生什么?
這是反向合並的變體,在將特征合並到母版之前,先將母版合並到要素中。 這個想法是將合並基礎向上移動,以減少第二次合並發生沖突的可能性。 使用 rebase 代替,我們還更改了歷史記錄,使該功能看起來比原來更接近 master。
如果我省略了最后一個命令 git merge feature-a 會發生什么
那么你不妨忽略一切。 最后一條命令是整個行動的目標。
換句話說,你問錯了問題。 問題不在於為什么需要在變基后進行合並。 這就是為什么您需要在合並之前進行變基。 答案是,你不會,但它會讓事情變得更好。
第一個命令 (rebase) 將 master 的更改引入到您的功能分支。 您也可以在此處使用合並。
第三個命令(merge)將feature-a分支的變化引入到master。 您不能在此處使用 rebase,因為它會更改其他人基於其分支的 master 上已有的提交。
如果不運行第三個:master 不會有 feature-a 的變化。
除了matt 的回答之外,首先值得考慮git rebase
含義。 為什么我們曾經使用過git rebase
? 只有一個真正的1 個答案:
因為git rebase
本質上是類固醇上的git cherry-pick
,並且使用git cherry-pick
允許我們在復制過程中更改提交,所以當我們 rebase 時,我們獲得了兩個能力:
首先,新提交通常存在於歷史中的不同“位置”(在 Git 提交圖中)。 我們實際上可能需要更努力地讓它們出現在“相同的地方”:默認的 rebase 操作是在我們選擇作為upstream
或--onto
參數的提交之后放置新的、復制的提交,這取決於我們如何運行git rebase
。 這通常是目標分支上的最新提交,這通常不是我們開始時的位置。
其次,使用git rebase -i
及其edit
模式,或者當 Git rebase 因合並沖突而暫停時,我們可以甚至必須更改rebase 操作暫停時正在進行的副本的效果。 我們進行此更改並恢復 rebase,並且“復制的”提交不再與原始提交完全相同,和/或具有不同的提交消息。 (有可能僅提交消息就是我們想要更改的一件事,在這種情況下,我們可以使用git rebase -i
的reword
操作。)
然而,最終,rebase 的結果是一組新的和改進的替換提交,修復了我們不喜歡原始提交的一些東西。 (如果 rebase 的結果沒有解決任何問題——或者讓事情變得更糟——那么我們應該撤消 rebase,這在 rebase 完成后很容易。2 )
一旦我們完成了 rebase,就可能不再需要真正的合並,即使在我們開始之前這樣的合並是必要的。 當我們所做的更改之一是將提交放入新的“歷史位置”時,就會發生這種情況。 最后,您運行的git merge
命令可能會執行 Git 所謂的快進合並而不是真正的合並。 快進合並根本不是合並(我希望 Git 沒有這樣稱呼它)。
有些人喜歡這種快進效果。 有些人沒有; 如果您在“不喜歡它”組中,您可以使用git merge --no-ff
強制 Git 進行真正的合並,即使可能進行快進非合並。 這完全是個人和/或團體偏好的問題。 但是,如果您或您的團隊喜歡快進效果,則可能需要 rebase 本身才能啟用快進效果。 這可能就是您進行變基的原因。
1不真實的答案是:“因為我的老板讓我這樣做”、“因為這是我們一直在做的”,以及其他類型的貨物崇拜編程。 😀這些是答案,它們不是我認為好的答案。
2在 rebase 完成后,我們可以運行git reset --hard ORIG_HEAD
,一切就好像我們從未開始 rebase 一樣。 或者,如果 rebase 在中間進行得非常糟糕,我們可以使用git rebase --abort
,它具有相同的效果:就好像我們根本沒有啟動 rebase。 這里有很多幕后工作,當然,這個過程可能無法恢復不在Git 中的文件。
讓我把你畫的圖片作為文字畫出來。 嗯,幾乎一樣。
我們從這個開始:
...--G--H <-- somebranch (HEAD), origin/somebranch
在您自己的存儲庫中。 您現在添加兩個新提交——我們將它們稱為K
和L
,跳過I
和J
以保留它們:
...--G--H <-- origin/somebranch
\
K--L <-- somebranch (HEAD)
此時,您已准備好將您的工作與其他人可能已經完成的任何工作結合起來,因此您可以運行git fetch origin
。 這會將其他人編寫的兩個新提交帶入您的存儲庫。 現在讓我們把它們畫進去:
I--J <-- origin/somebranch
/
...--G--H
\
K--L <-- somebranch (HEAD)
這類似於您的“變基前”圖片,除了我在兩側使用相同的名稱( somebranch
),而不是名稱main
。 你的 Git 重命名它們的分支名稱以作為你的遠程跟蹤名稱; 這就是為什么你有一個origin/somebranch
,這就是我們選擇兩個新提交的地方。
為了將我們的工作 ( KL
) 與他們的工作 ( IJ
) 結合起來,我們必須做出選擇:rebase、merge 或兩者的某種組合。 (沒有多少人做最后一個,因為它是相當多的額外工作,而且盡快得到一個勉強夠用的答案通常很重要,而不是下周精心設計的內部答案。😀沒有人看看軟件的內部結構,買家只需支付昨天完成的費用!)
常規合並是最簡單的答案。 Git 將:
H
的快照與提交J
的快照進行比較,以查看它們更改了什么;H
的快照與提交L
的快照進行比較,以查看我們更改了什么;H
的快照。 這會保留我們的更改並添加他們的更改,或者——等效地——保留他們的更改並添加我們的更改。 生成的合並提交M
如下所示:
I--J <-- origin/somebranch
/ \
...--G--H M <-- somebranch (HEAD)
\ /
K--L
並且由於提交M
出現在提交J
,換句話說,不會丟失他們的任何工作,以及在提交L
,也保留我們的工作,我們現在可以git push
提交M
git push
提交到origin
上的某個共享存儲庫。 (提交M
將自動帶來提交KL
。)
但是,對於那些不喜歡合並的人,我們可以改為將我們的提交K
復制到一個新的和改進的提交K'
。 我們首先創建一個臨時分支名稱:
I--J <-- temp (HEAD), origin/somebranch
/
...--G--H
\
K--L <-- somebranch
然后,我們讓 Git 復制提交K
的效果——通過比較H
-vs- K
來查看我們更改了什么——並將其應用到提交J
,我們現在所處的位置。 如果一切順利,Git 將獲取合並的結果——這是一次合並,就像git merge
——並進行一個新的非合並、普通提交,其對J
的影響就像K
對H
的影響一樣。 我們將這個新提交稱為K'
,並重新使用K
的提交消息:
K' <-- temp (HEAD)
/
I--J <-- origin/somebranch
/
...--G--H
\
K--L <-- somebranch
我們現在需要 Git 對 commit L
做同樣的事情。 這一次, git cherry-pick
使用的內部合並將比較提交K
與L
以找出我們更改的內容,並比較K
-vs- K'
以找出“他們”更改的內容。 (這真的是他們 -和 - 我們改變了: K'
是I+J+K
,畢竟,關於我們都從H
開始的地方。但 Git 仍然將其稱為--theirs
。)
如果一切順利,這個挑選合並的結果是一個新的提交L'
:
K'-L' <-- temp (HEAD)
/
I--J <-- origin/somebranch
/
...--G--H
\
K--L <-- somebranch
git rebase
的最后一部分包括從舊提交鏈(本例中為KL
“剝離分支名稱”,並將其粘貼到新鏈的末尾:
K'-L' <-- somebranch (HEAD)
/
I--J <-- origin/somebranch
/
...--G--H
\
K--L [abandoned]
如果我們去掉[abandoned]
部分,這或多或少與您作為“之后”圖片繪制的相同。
這意味着你有正確的圖片。
現在,讓我們假設所有這些工作的目標是能夠使名稱main
向前發展。 名稱main
當前指向提交J
。 也就是說,我們應該有這個,而不是我們圖片中的origin/somebranch
:
K'-L' <-- somebranch (HEAD)
/
I--J <-- main
/
...--G--H
rebase 已經完成了它的工作:它復制了H
KL
到現在J
之后的新的和改進的提交中。 但名字main
沒有移動。
做一個:
git checkout main
git merge somebranch
告訴 Git 確定是否需要真正的合並。 當有一組實際的分支提交時需要真正的合並,就像我們在提交M
時的“常規合並”示例一樣。 在另一種情況下它是可選的,我們可以“向前滑動名稱main
”(並且由於繪圖中的扭結而向上滑動,這只是因為這是有限的 ASCII 藝術)。
可以進行快進操作的合並命令的默認操作是執行快進而不是合並。 結果是:
K'-L' <-- main (HEAD), somebranch
/
I--J
/
...--G--H
請注意, HEAD
現在附加到main
,因為我們在運行git merge somebranch
之前運行了git checkout main
。
我們不必在任何絕對意義上這樣做,因為 Git不關心分支名稱。 我們現在可以開始使用名稱somebranch
作為主分支,甚至可以完全刪除名稱main
:
K'-L' <-- somebranch (HEAD)
/
I--J
/
...--G--H
但是,如果我們,作為普通人,不能破解它——我們可能不能——我們應該git checkout main
並讓 Git 向前滑動名稱,然后可能刪除名稱somebranch
:
K'-L' <-- main (HEAD)
/
I--J
/
...--G--H
並停止繪制扭結,並從復制的 K-and-L 提交中刪除主要標記:
...--G--H--I--J--K--L <-- main (HEAD)
現在看起來我們在提交K
和L
之后,在看到時提交了IJ
。 事實上,在我們訪問IJ
之前,我們制作了原件——不叫K
和L
,並在一段時間前放棄了。 但是我們非常確定我們不再需要那些原件,以至於我們願意讓再次找到它們變得不可能,或者至少是痛苦的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.