簡體   English   中英

上次提交更改時Git rebase

[英]Git rebase when previous commit changed

我經常發現自己正在處理不同git分支上的兩個不同的工作票,但是一個依賴於另一個,如下所示:

* later-branch
|
* earlier-branch
|
* some prior commit
|

(每個都是一個提交,因為我們使用的是gerrit,但是這個問題也可能適用於每個提交的多個提交。)早期的分支可能正在進行審核,因此我可能必須返回並在某個時候修改它使用git commit --amend 必須發生的事情,這將分叉歷史:

* earlier-branch
|
|  * later-branch
|  |
|  * previous version of earlier-branch
| /
* some prior commit
|

在這一點上,我要變基的later-branch上的新版本之上earlier-branch 但是,如果我只是做一個git checkout later-branch隨后是git rebase earlier-branch ,它總是得到沖突,因為(我覺得)它必須先申請的previous version of earlier-branch提交到最新版本的earlier-branch

我最終做的是git checkout earlier-branch -b new-later-branch-name然后是git cherry-pick later-branchgit br -D later-branch 這是一種痛苦。 誰能建議一個更好的方法來處理這個?

我看到兩種簡單的方法來做到這一點。

第一個,最好的選擇是在交互模式下使用git rebase 要做到這一點,你會這樣做

git checkout later-branch
git rebase -i earlier-branch

在彈出的屏幕中,您將選擇drop previous version of earlier-branch

drop efb1c19 previous version of earlier-branch
pick a25ba16 later-branch

# Rebase 65f3afc..a25ba16 onto 65f3afc (2 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
...

這將在earlier-branch later-branch的頂部重新earlier-branch ,提供以下樹:

* later-branch
|
* earlier-branch
|
|  * previous version of earlier-branch
| /
* some prior commit
|

另一種選擇是簡單地做一個git cherry-pick 如果你這樣做:

git checkout earlier-branch
git cherry-pick later-branch

你會得到以下樹:

* earlier-branch -> cherry-picked commit 1
|
* earlier-branch -> amended commit 0, now commit 2
|
|  * later-branch -> commit 1
|  |
|  * previous version of earlier-branch -> commit 0
| /
* some prior commit
|

因此,實際上,這將產生您想要的結果,但它將提前earlier-branch 如果分支名稱對您很重要,您可以相應地重命名和重置它們。

除了交互式rebase(如houtanb的回答 ),還有兩種方法可以更自然地執行此操作:

  • 使用git rebase --onto ,或
  • 使用“fork-point”代碼(自Git 2.0版以來在Git中)。

要使用后者,可以在later-branch上運行git rebase --fork-point earlier-branch later-branch

(你可以改為將early earlier-branch設置為later-branch earlier-branch上游 later-branch可能只是暫時的,在git rebase期間 - 然后在later-branch時運行git rebase 。原因是--fork-point是使用自動上游模式時的默認值 ,但在使用git rebase的顯式<upstream>參數時必須顯式請求 。)

不幸的是,最后一個看起來特別神奇,特別是那些剛接觸Git的人。 幸運的是,你的圖表中有理解它的種子 - 以及git rebase --onto

定義叉點

讓我們把你上面繪制的東西轉向側面,然后再轉動一點。 這給了我一些繪制分支名稱的空間。 我會用圓o節點或大寫字母和數字替換每次提交的* s。 我將向后一個分支添加第三個提交C ,以便進行說明。

:
 .
  \
   o
    \
     A1   <-- earlier-branch
      \
       B--C   <-- later-branch

現在,無論出於何種原因,您都被迫將提交A1復制到新提交A2 ,並將分支標簽earlier-branch移動到指向新副本:

讓我們把你上面繪制的東西轉向側面,然后再轉動一點。 這給了我一些繪制分支名稱的空間。 我將使用圓形o節點或大寫字母替換每次提交的* s。

:
 .
  \
   o--A2  <-- earlier-branch
    \
     A1
      \
       B--C   <-- later-branch

如果只有Git會記住提交A1存在,因為earlier-branch 用於包含提交A1 ,我們可以告訴Git:“當復制later-branch ,刪除現在仍在其上的任何提交,但過去僅僅是因為earlier-branch “。

但Git 確實記得這個,至少30天,默認情況下。 Git具有reflogs -logs,用於存儲在每個引用中 (包括常規分支和Git的所謂遠程跟蹤分支 )。 如果我們將reflog信息添加到繪圖中,它看起來像這樣:

:
 .
  \
   o--A2   <-- earlier-branch
    \
     A1    <-- [earlier-branch@{1}]
      \
       B--C   <-- later-branch

實際上,如果由於某種原因你必須將A2復制到A3 ,那么該圖只會增加另一個reflog條目,重新編號現有的:

:
 .   A3    <-- earlier-branch
  \ /
   o--A2   <-- [earlier-branch@{1}]
    \
     A1    <-- [earlier-branch@{2}]
      \
       B--C   <-- later-branch

fork-point代碼的作用是掃描reflog以獲取其他引用,例如early earlier-branch ,並找到這些提交(在這種情況下, A1 -it實際上找到A1A2 ,在后一種情況下,但是然后winnows它下到兩個分支上的A1 ;另見Git rebase - 在fork-point模式下提交select 然后它為你運行git rebase --onto ,就像你手動運行一樣:

git rebase --onto earlier-branch hash-of-A1

這讓我們了解了--onto參數的工作原理。

定期rebase,沒有--onto

通常,您可以使用一個參數運行git rebase ,如git rebase branch-name ,甚至根本沒有參數。 根本沒有參數, git rebase使用當前分支的上游設置。 使用branch-name參數, git rebase調用該參數<upstream> (作為一個奇怪的副作用,這也是 - 因為Git版本2.0無論如何 - 自動啟用或禁用--fork-point選項,要求你使用顯式--no-fork-point--fork-point如果你想另一種模式。)

在任何情況下,如果你沒有指定一個用於兩個目的,Git會自動使用<upstream> - 選擇。 一種是限制將被復制的提交集:Git將考慮通過運行復制列出的提交集:

git rev-list <upstream>..HEAD

要以更友好的方式查看它們,請使用git log或我首選的方法git log --oneline --decorate --graph ,而不是git rev-list here:

git log --oneline --decorate --graph earlier-branch..HEAD

理想情況下,我們會在這里看到提交BC ,首先列出C (Git必須使用--reverse以確保它首先復制B )。 但是,如果您將A1復制到A2和/或復制到A3 ,並移動了分支earlier-branch ,我們將看到所有A1BC (Git不包括A2A3earlier-branch點為准 - 但它們無論如何都不在列表中。然后使用排除的A2A3排除A1 之前的提交,這就是為什么我們看不到這些。)

<upstream>分支名稱(或提交哈希)的另一個目的是選擇副本的位置 當我們復制一個或多個提交時,每個復制的提交必須在一些現有提交之后進行。 <upstream>參數提供將作為我們復制的第一個提交的父級的提交的ID。

因此,運行git rebase earlier-branch會使Git列表按順序提交A1BC 然后使用“分離的HEAD”模式 - 將A1復制到earlier-branch

:
 .       A1'  <-- HEAD
  \     /
   o--A2   <-- earlier-branch
    \
     A1    <-- [earlier-branch@{1}]
      \
       B--C   <-- later-branch

然后將B復制到A1'

:
 .       A1'--B'  <-- HEAD
  \     /
   o--A2   <-- earlier-branch
    \
     A1    <-- [earlier-branch@{1}]
      \
       B--C   <-- later-branch

調整基線然后復制CC'和移動分支標簽, later-branch ,到哪里HEAD卷起,再附上你的頭在這個過程中:

:
 .       A1'--B'--C'  <-- later-branch (HEAD)
  \     /
   o--A2   <-- earlier-branch
    \
     A1    <-- [earlier-branch@{1}]
      \
       B--C   <-- [later-branch@{1}]

--onto參數讓你告訴Git 副本去哪里

使用--ontogit rebase

當您添加--onto ,您告訴Git rebase將副本放在何處。 這釋放了<upstream>參數,現在它只指定不要復制的內容! 所以現在你可以自由地告訴Git:“通過寫入來復制提交A1 之后的所有內容”:

git rebase --onto earlier-branch <hash-of-A1>

Git做了它常用的事情,列出要復制的提交( BC ),從later-branch分離你的HEAD,一次復制一個提交,副本跟在earlier-branch的尖端之后,最后移動名字later-branch重新連接你的HEAD。

這正是我們想要的,都是半自動完成的:我們告訴Git 不要復制A1本身,所以它只復制BC

當我們指定上游時,就像在git rebase earlier-branch ,Git 禁用 fork-point模式。 如果我們明確啟用它,Git將通過earlier-branch reflog。 只要提交A1的reflog條目尚未到期,Git就會發現A1曾經在earlier-branch並且將使用--onto為我們從to-copy列表中丟棄它。

請注意,這里有一點危險。 如果我們真的想要 A1會怎么樣呢,例如,如果我們支持earlier-branchA1只是因為我們意識到A1不屬於另一個分支? Git仍然認為我們將它復制到其他一些提交中,並且現在不想復制它,並且會拋棄列表。 幸運的是,你總是可以撤消一個rebase:rebase根本不丟棄任何東西,它只是復制 然后它會更新一個分支,它將以前的值保存在分支的reflog中。 但是通過reflogs釣魚,試圖找到一組特定的提交,在一個完全相同的提交迷宮中,並不是很有趣 - 所以在運行rebase之前考慮一下是否明智--fork-point無論有沒有--fork-point

邊注

在一些(罕見)情況下,Git的你不必做任何事情(無分叉點模式,無需手動--onto分離,沒有--interactive )。 具體來說,如果補丁本身根本沒有改變,但只有提交消息中措辭發生了變化,Git將檢測已經復制的提交並跳過它。 這是因為git rebase實際上使用git rev-list對稱差異模式和--cherry-pick --right-only --no-merges選項。 也就是說,而不是:

git rev-list <upstream>..HEAD

Git實際上運行:

git rev-list --cherry-pick --right-only --no-merges <upstream>...HEAD

(注意三個點)。 不過,我沒時間在這里詳細介紹。

暫無
暫無

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

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