簡體   English   中英

為什么在變基后需要合並?

[英]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 -ireword操作。)

然而,最終,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

在您自己的存儲庫中。 您現在添加兩個新提交——我們將它們稱為KL ,跳過IJ以保留它們:

...--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的影響就像KH的影響一樣。 我們將這個新提交稱為K' ,並重新使用K的提交消息:

               K'  <-- temp (HEAD)
              /
          I--J   <-- origin/somebranch
         /
...--G--H
         \
          K--L   <-- somebranch

我們現在需要 Git 對 commit L做同樣的事情。 這一次, git cherry-pick使用的內部合並將比較提交KL以找出我們更改的內容,並比較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)

現在看起來我們在提交KL之后,在看到時提交了IJ 事實上,我們訪問IJ之前,我們制作了原件——不叫KL ,並在一段時間放棄了。 但是我們非常確定我們不再需要那些原件,以至於我們願意讓再次找到它們變得不可能,或者至少是痛苦的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM