English ▾ 主題 ▾ 最新版本 ▾ git-rebase 最後更新於 2.54.0

名稱

git-rebase - 在另一個基底端點之上重新套用提交

概要

git rebase [-i | --interactive] [<options>] [--exec <cmd>]
	[--onto <newbase> | --keep-base] [<upstream> [<branch>]]
git rebase [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]
	--root [<branch>]
git rebase (--continue|--skip|--abort|--quit|--edit-todo|--show-current-patch)

描述

將一系列提交移植到不同的起點。您也可以使用 git rebase 來重新排序或合併提交:請參閱下方的「互動模式」了解具體作法。

例如,假設您一直在如下歷史中的 topic 分支上工作,並且想要「追上」 master 分支的工作進度。

          A---B---C topic
         /
    D---E---F---G master

您想將自從與 master 分離以來在 topic 上所做的提交(即 A、B 和 C)移植到當前 master 的頂端。在檢出 topic 分支時,您可以透過執行 git rebase master 來完成此操作。如果您想在另一個分支上對 topic 進行重基,git rebase master topicgit checkout topic && git rebase master 的簡寫。

                  A'--B'--C' topic
                 /
    D---E---F---G master

如果在執行過程中發生合併衝突,git rebase 會停在第一個有問題的提交並留下衝突標記。如果發生這種情況,您可以採取以下行動之一:

  1. 解決衝突。您可以使用 git diff 找到標記 (<<<<<<) 並進行編輯以解決衝突。對於您編輯的每個文件,您需要告訴 Git 衝突已解決。您可以使用 git add <filename> 將衝突標記為已解決。解決所有衝突後,您可以繼續重基過程:

    git rebase --continue
  2. 停止 git rebase 並將您的分支恢復到原始狀態:

    git rebase --abort
  3. 跳過導致合併衝突的提交:

    git rebase --skip

如果您未指定重基到的 <upstream>(上游),則會使用 branch.<name>.remotebranch.<name>.merge 選項中設定的上游(詳情請參閱 git-config[1]),並假設使用 --fork-point 選項。如果您目前不在任何分支上,或者當前分支沒有設定上游,則重基將中止。

這是對 git rebase <upstream> 所做操作的簡化描述:

  1. 列出當前分支自從與 <upstream> 分叉以來的所有提交,且這些提交在 <upstream> 中沒有等效的提交。

  2. 使用等同於 git checkout --detach <upstream> 的方式檢出 <upstream>

  3. 按順序逐一重放提交。這類似於為每個提交執行 git cherry-pick <commit>。關於合併的處理方式,請參閱「重基合併」章節。

  4. 使用等同於 git checkout -B <branch> 的方式將您的分支更新至指向最後一個提交。

注意
開始重基時,ORIG_HEAD 會被設定為指向待重基分支的頂端提交。但是,如果在重基期間使用了其他會更改 ORIG_HEAD 的命令(如 git reset),則無法保證 ORIG_HEAD 在重基結束時仍指向該提交。不過,可以透過當前分支的參照日誌 (reflog) 訪問前一個分支頂端(即 @{1},請參閱 gitrevisions[7])。

使用 --ONTO 移植主題分支

以下說明如何使用 rebase --onto 將基於某個分支的主題分支移植到另一個分支,以假裝您是從後者分支分叉出該主題分支的。

首先假設您的 topic 是基於 next 分支的。例如,在 topic 中開發的功能依賴於 next 中的某些功能。

    o---o---o---o---o  master
         \
          o---o---o---o---o  next
                           \
                            o---o---o  topic

我們想讓 topicmaster 分支分叉出來;例如,因為 topic 所依賴的功能已被合併到更穩定的 master 分支中。我們希望我們的結構如下所示:

    o---o---o---o---o  master
        |            \
        |             o'--o'--o'  topic
         \
          o---o---o---o---o  next

我們可以使用以下命令實現:

git rebase --onto master next topic

--onto 選項的另一個例子是重基分支的一部分。如果我們有以下情況:

                            H---I---J topicB
                           /
                  E---F---G  topicA
                 /
    A---B---C---D  master

那麼命令:

git rebase --onto master topicA topicB

會導致:

                 H'--I'--J'  topicB
                /
                | E---F---G  topicA
                |/
    A---B---C---D  master

這在 topicB 不依賴於 topicA 時非常有用。

重基也可以移除一段範圍的提交。如果我們有以下情況:

    E---F---G---H---I---J  topicA

那麼命令:

git rebase --onto topicA~5 topicA~3 topicA

將導致移除提交 F 和 G:

    E---H'---I'---J'  topicA

如果 F 和 G 存在某種缺陷,或者不應成為 topicA 的一部分,這會很有用。請注意,--onto 的參數和 <upstream> 參數可以是任何有效的提交對象 (commit-ish)。

模式選項

本節中的選項不能與任何其他選項(包括彼此)同時使用:

--continue

解決合併衝突後重新開始重基過程。

--skip

跳過當前補丁 (patch) 並重新開始重基過程。

--abort

中止重基操作並將 HEAD 重設回原始分支。如果在開始重基操作時提供了 <branch>,則 HEAD 將重設為 <branch>。否則,HEAD 將重設為重基操作開始時的位置。

--quit

中止重基操作,但 HEAD 不會重設回原始分支。索引 (index) 和工作區也保持不變。如果使用 --autostash 建立了臨時儲藏項,它將被儲存到儲藏列表中。

--edit-todo

在互動式重基期間編輯待辦列表 (todo list)。

--show-current-patch

在互動式重基中或因衝突停止重基時顯示當前補丁。這相當於 git show REBASE_HEAD

選項

--onto <newbase>

建立新提交的起點。如果未指定 --onto 選項,則起點為 <upstream>。可以是任何有效的提交,而不僅僅是現有的分支名稱。

作為特殊情況,如果有且僅有一個合併基底,您可以使用 "A...B" 作為 A 和 B 合併基底的快捷方式。您可以省略 A 或 B 其中的一個(最多一個),此時預設為 HEAD。

範例請參閱上方的「使用 --ONTO 移植主題分支」。

--keep-base

將建立新提交的起點設定為 <upstream><branch> 的合併基底。執行 git rebase --keep-base <upstream> <branch> 相當於執行 git rebase --reapply-cherry-picks --no-fork-point --onto <upstream>...<branch> <upstream> <branch>

此選項在開發基於上游分支的功能時非常有用。在開發功能期間,上游分支可能會前進,此時與其不斷重基到上游頂端,不如保持基底提交不變。由於基底提交不變,此選項會隱含 --reapply-cherry-picks 以避免遺失提交。

儘管此選項和 --fork-point 都會尋找 <upstream><branch> 之間的合併基底,但此選項使用合併基底作為建立新提交的「起點」,而 --fork-point 使用合併基底來確定要重基的「提交集合」。

另請參閱下方的「不相容的選項」。

<upstream>

要進行比較的上游分支。可以是任何有效的提交,而不僅僅是現有的分支名稱。預設為當前分支設定的上游分支。

<branch>

工作分支;預設為 HEAD

--apply

使用套用 (apply) 策略進行重基(內部呼叫 git-am)。一旦合併後端能處理套用後端的所有功能,此選項在未來可能會變成無操作 (no-op)。

另請參閱下方的「不相容的選項」。

--empty=(drop|keep|stop)

如何處理起初不為空,且不是任何上游提交的乾淨挑選 (cherry-pick),但在重基後變為空的提交(因為它們包含的是已在上游變更中的子集):

drop

該提交將被丟棄。這是預設行為。

keep

該提交將被保留。除非同時指定了 -i/--interactive,否則指定 --exec 時會隱含此選項。

stop
ask

重基會在套用該提交時停止,允許您選擇是否丟棄它、進一步編輯檔案,或僅提交空的變更。指定 -i/--interactive 時會隱含此選項。askstop 的已棄用同義詞。

請注意,起初即為空的提交會被保留(除非指定了 --no-keep-empty),而作為乾淨挑選的提交(由 git log --cherry-mark ... 判定)會在初步步驟中被偵測並丟棄(除非傳遞了 --reapply-cherry-picks--keep-base)。

另請參閱下方的「不相容的選項」。

--no-keep-empty
--keep-empty

在結果中不要保留重基前就為空的提交(即與其父提交相比沒有任何更改的提交)。預設是保留起初就為空的提交,因為建立此類提交需要向 git commit 傳遞 --allow-empty 覆蓋旗標,這代表使用者非常有意識地建立並想要保留該提交。

使用此旗標的情況可能很少見,因為您可以透過啟動互動式重基並刪除對應行來手動移除空提交。此旗標是作為方便的捷徑存在,例如用於外部工具產生許多空提交且您想全部移除的情況。

對於起初不為空但在重基後變為空的提交,請參閱 --empty 旗標。

另請參閱下方的「不相容的選項」。

--reapply-cherry-picks
--no-reapply-cherry-picks

重新套用所有與上游提交相同的乾淨挑選,而不是預先丟棄它們。(如果這些提交在重基後因為包含已在上游的變更子集而變為空,則對其行為由 --empty 旗標控制。)

在沒有 --keep-base 的情況下(或如果給出了 --no-reapply-cherry-picks),這些提交將被自動丟棄。由於這需要讀取所有上游提交,因此在具有大量需讀取上游提交的儲存庫中,這可能非常耗時。使用 merge 後端時,將為每個丟棄的提交發出警告(除非給出了 --quiet)。除非將 advice.skippedCherryPicks 設定為 false,否則也會發出建議(見 git-config[1])。

--reapply-cherry-picks 允許重基放棄讀取所有上游提交,從而潛在地提高效能。

另請參閱下方的「不相容的選項」。

--allow-empty-message

無操作。以前對訊息為空的提交進行重基會失敗,此選項可覆蓋該行為。現在訊息為空的提交不會導致重基停止。

另請參閱下方的「不相容的選項」。

-m
--merge

使用合併策略進行重基(預設值)。

請注意,重基合併的工作方式是在 <upstream> 分支之上重放工作分支的每個提交。因此,當發生合併衝突時,報告為 ours(我們方)的一側是到目前為止重基的系列(以 <upstream> 開始),而 theirs(他們方)是工作分支。換句話說,兩側是互換的。

另請參閱下方的「不相容的選項」。

-s <strategy>
--strategy=<strategy>

使用給定的合併策略,而不是預設的 ort。這會隱含 --merge

由於 git rebase 使用給定策略在 <upstream> 分支頂端重放工作分支的每個提交,因此使用 ours 策略只會清空來自 <branch> 的所有補丁,這幾乎沒有意義。

另請參閱下方的「不相容的選項」。

-X <strategy-option>
--strategy-option=<strategy-option>

將 <strategy-option> 傳遞給合併策略。這會隱含 --merge,且如果未指定策略,則預設為 -s ort。請注意上述 -m 選項中提到的 ourstheirs 的反轉。

另請參閱下方的「不相容的選項」。

--rerere-autoupdate
--no-rerere-autoupdate

在 rerere 機制重複使用已記錄的衝突解決方案來更新工作區中的檔案後,允許其同時更新索引。使用 --no-rerere-autoupdate 可以在使用單獨的 git-add[1] 將結果提交到索引之前,再次檢查 git-rerere[1] 的操作並捕捉潛在的錯誤合併。

-S[<keyid>]
--gpg-sign[=<keyid>]
--no-gpg-sign

對提交進行 GPG 簽署。keyid 參數是選填的,預設為提交者身分;如果指定,必須緊接在選項後方且不留空格。--no-gpg-sign 可用於取消 commit.gpgSign 設定變數和之前的 --gpg-sign 選項。

-q
--quiet

安靜模式。隱含 --no-stat

-v
--verbose

詳細模式。隱含 --stat

--stat

顯示自上次重基以來上游變更的差異統計 (diffstat)。差異統計也受 rebase.stat 設定選項控制。

-n
--no-stat

在重基過程中不顯示差異統計。

--no-verify

此選項會繞過 pre-rebase 掛鉤。另請參閱 githooks[5]

--verify

允許執行 pre-rebase 掛鉤,這是預設行為。此選項可用於覆蓋 --no-verify。另請參閱 githooks[5]

-C<n>

確保在每次更改前後至少有 <n> 行的周邊上下文匹配。當周邊上下文行數較少時,它們必須全部匹配。預設情況下不忽略任何上下文。隱含 --apply

另請參閱下方的「不相容的選項」。

--no-ff
--force-rebase
-f

逐一重放所有被重基的提交,而不是快速前進 (fast-forward) 過未更改的提交。這確保了重基分支的整個歷史紀錄都是由新提交組成的。

在還原主題分支合併後,您可能會發現這很有用,因為此選項會以新提交重新建立主題分支,以便它可以成功重新合併,而無需「還原該還原」(詳情請參閱 revert-a-faulty-merge 指南)。

--fork-point
--no-fork-point

在計算哪些提交是由 <branch> 引入時,使用參照日誌 (reflog) 在 <upstream><branch> 之間尋找更好的共同祖先。

--fork-point 啟用時,將使用 fork_point 代替 <upstream> 來計算要重基的提交集,其中 fork_pointgit merge-base --fork-point <upstream> <branch> 命令的結果(見 git-merge-base[1])。如果 fork_point 結果為空,則將使用 <upstream> 作為備案。

如果命令列指定了 <upstream>--keep-base,則預設為 --no-fork-point,否則預設為 --fork-point。另請參閱 git-config[1] 中的 rebase.forkpoint

如果您的分支基於 <upstream>,但 <upstream> 已被倒回 (rewound) 且您的分支包含已被丟棄的提交,則此選項可與 --keep-base 配合使用以從您的分支中丟棄這些提交。

另請參閱下方的「不相容的選項」。

--ignore-whitespace

在嘗試調和差異時忽略空白差異。目前,每個後端都實作了這種行為的近似值:

apply 後端

套用補丁時,忽略上下文行中的空白變更。不幸的是,這意味著如果被補丁替換的「舊」行與現有檔案僅在空白上有差異,您將會遇到合併衝突,而不是成功的補丁套用。

merge 後端

合併時將僅有空白變更的行視為未更改。不幸的是,這意味著任何旨在修改空白的補丁區塊都將被丟棄,即使另一側沒有衝突的變更。

--whitespace=<option>

此旗標傳遞給負責套用補丁的 git apply 程式(見 git-apply[1])。隱含 --apply

另請參閱下方的「不相容的選項」。

--committer-date-is-author-date

使用待重基提交的作者日期作為提交日期,而不是使用目前時間。此選項隱含 --force-rebase

警告
歷史瀏覽機制假設提交的時間戳記是非遞減的。您應該考慮是否真的需要使用此選項。您應該只在將提交重基到一個比您正在套用的最舊提交(就作者日期而言)還要舊(就提交日期而言)的基底之上時,才使用此選項來覆蓋提交者日期。
--ignore-date
--reset-author-date

使用目前時間作為重基後提交的作者日期,而不是使用原始提交的作者日期。此選項隱含 --force-rebase

另請參閱下方的「不相容的選項」。

--signoff

在所有重基後的提交中加入 Signed-off-by 結尾標籤。請注意,如果給出了 --interactive,則只有被標記為挑選 (pick)、編輯 (edit) 或修改訊息 (reword) 的提交才會加入此標籤。

另請參閱下方的「不相容的選項」。

--trailer=<trailer>

透過 git-interpret-trailers[1] 處理,並在每個重基後的提交訊息中附加給定的結尾標籤。此選項隱含 --force-rebase

另請參閱下方的「不相容的選項」。

-i
--interactive

列出即將重基的提交清單。允許使用者在重基前編輯該清單。此模式也可用於分割提交(參見下方的「分割提交」)。

提交列表格式可以透過設定 rebase.instructionFormat 設定選項來更改。自定義的指令格式會自動在前方加上提交雜湊值 (hash)。

另請參閱下方的「不相容的選項」。

-r
--rebase-merges[=(rebase-cousins|no-rebase-cousins)]
--no-rebase-merges

預設情況下,重基會簡單地從待辦列表中丟棄合併提交,並將重基後的提交放入單一線性分支。使用 --rebase-merges,重基將透過重新建立合併提交,嘗試保留待重基提交中的分支結構。這些合併提交中任何已解決的合併衝突或手動修改都必須手動重新解決/套用。--no-rebase-merges 可用於取消 rebase.rebaseMerges 設定選項和先前的 --rebase-merges

重基合併時有兩種模式:rebase-cousinsno-rebase-cousins。如果未指定模式,則預設為 no-rebase-cousins。在 no-rebase-cousins 模式下,不以 <upstream> 為直接祖先的提交將保留其原始分支點,即預設情況下,會被 git-log[1]--ancestry-path 選項排除的提交將保留其原始血統。在 rebase-cousins 模式下,此類提交則會被重基到 <upstream>(或 <onto>,如果已指定)之上。

目前僅能使用 ort 合併策略重新建立合併提交;不同的合併策略僅能透過顯式的 exec git merge -s <strategy> [...] 命令來使用。

另請參閱下方的「重基合併」和「不相容的選項」。

-x <cmd>
--exec <cmd>

在最終歷史記錄中建立提交的每一行後附加 "exec <cmd>"。<cmd> 將被解釋為一個或多個 shell 命令。任何失敗的命令都會中斷重基,並傳回結束碼 1。

您可以透過使用具有多個命令的單一 --exec 實例來執行多個命令:

git rebase -i --exec "cmd1 && cmd2 && ..."

或是提供多個 --exec

git rebase -i --exec "cmd1" --exec "cmd2" --exec ...

如果使用了 --autosquash,則不會為中間提交附加 exec 行,而只會出現在每個 squash/fixup 系列的末尾。

這在內部使用了 --interactive 機制,但可以在沒有顯式指定 --interactive 的情況下執行。

另請參閱下方的「不相容的選項」。

--root

重基所有從 <branch> 可達的提交,而不是使用 <upstream> 進行限制。這允許您重基分支上的根提交 (root commit)。

另請參閱下方的「不相容的選項」。

--autosquash
--no-autosquash

自動將具有特殊格式訊息的提交壓縮 (squash) 到之前正在重基的提交中。如果提交訊息以 "squash! "、"fixup! " 或 "amend! " 開頭,則標題的其餘部分將被視為提交識別碼,如果它與先前提交的標題或雜湊值匹配,則視為匹配。如果沒有完全匹配的提交,則會考慮識別碼與提交標題開頭的匹配。

在重基待辦列表中,squash、fixup 和 amend 提交的操作會分別從 pick 更改為 squashfixupfixup -C,並被移至它們要修改的提交之後。可以使用 --interactive 選項在繼續之前查看和編輯待辦列表。

建立帶有壓縮標記之提交的推薦方法是使用 git-commit[1]--squash--fixup--fixup=amend:--fixup=reword: 選項,這些選項將目標提交作為參數,並自動從中填寫新提交的標題。

將設定變數 rebase.autoSquash 設定為 true 會預設為互動式重基啟用自動壓縮。--no-autosquash 選項可用於覆蓋該設定。

另請參閱下方的「不相容的選項」。

--autostash
--no-autostash

在操作開始前自動建立一個臨時儲藏項 (stash entry),並在操作結束後套用它。這意味著您可以在髒 (dirty) 工作區上執行重基。但是,請謹慎使用:成功重基後的最終儲藏套用可能會導致非顯而易見的衝突。

--reschedule-failed-exec
--no-reschedule-failed-exec

自動重新編排失敗的 exec 命令。這僅在互動模式(或提供 --exec 選項時)有意義。

此選項在重基開始後套用。它會在整個重基過程中保留,依序根據提供給初始 git rebase 的命令列選項、rebase.rescheduleFailedExec 設定(見 git-config[1] 或下方的「組態」),否則預設為 false。

在整個重基過程中記錄此選項是一項便利功能。否則,在叫用 git rebase --continue 時,如果存在 rebase.rescheduleFailedExec=true 設定,則會覆蓋開始時顯式指定的 --no-reschedule-failed-exec。目前,您無法將 --[no-]reschedule-failed-exec 傳遞給 git rebase --continue

--update-refs
--no-update-refs

自動強制更新指向正在重基之提交的任何分支。任何在工作區中檢出的分支都不會以這種方式更新。

如果設定了設定變數 rebase.updateRefs,則此選項可用於覆蓋並停用此設定。

另請參閱下方的「不相容的選項」。

不相容的選項

以下選項:

  • --apply

  • --whitespace

  • -C

與以下選項不相容:

  • --merge

  • --strategy

  • --strategy-option

  • --autosquash

  • --rebase-merges

  • --interactive

  • --exec

  • --no-keep-empty

  • --empty=

  • --[no-]reapply-cherry-picks 當不與 --keep-base 連用時

  • --update-refs

  • --root 當不與 --onto 連用時

  • --trailer

此外,以下成對的選項不相容:

  • --keep-base 和 --onto

  • --keep-base 和 --root

  • --fork-point 和 --root

行為差異

git rebase 有兩個主要的後端:applymerge。(apply 後端以前稱為 am 後端,但該名稱導致了混淆,因為它看起來像動詞而不是名詞。此外,merge 後端以前稱為互動式後端,但現在也用於非互動式情況。兩者都根據支撐各自底層功能的名稱重新命名。)這兩個後端的行為存在一些細微差別:

空提交

apply 後端遺憾地會丟棄有意識建立的空提交(即起初即為空的提交),儘管這在實踐中很少見。它還會丟棄變為空的提交,且沒有控制此行為的選項。

merge 後端預設保留有意識建立的空提交(儘管使用 -i 時,它們會在待辦列表編輯器中被標記為空,或者可以使用 --no-keep-empty 自動丟棄)。

與 apply 後端類似,除非指定了 -i/--interactive(在這種情況下它會停止並詢問使用者該怎麼做),否則 merge 後端預設會丟棄變為空的提交。merge 後端還有一個 --empty=(drop|keep|stop) 選項,用於更改處理變為空提交的行為。

目錄重新命名偵測

由於缺乏準確的樹狀資訊(源於使用補丁中有限的資訊建構虛假祖先),apply 後端停用了目錄重新命名偵測。停用目錄重新命名偵測意味著如果歷史的一側重新命名了目錄,而另一側在舊目錄中新增了新檔案,則在重基時,新檔案將留在舊目錄中,而不會收到您可能想將這些檔案移至新目錄的警告。

目錄重新命名偵測在 merge 後端中有效,並在這種情況下為您提供警告。

上下文 (Context)

apply 後端的工作方式是建立一系列補丁(內部呼叫 format-patch),然後按順序套用補丁(內部呼叫 am)。補丁由多個區塊組成,每個區塊都有行號、上下文區域和實際更改。行號必須帶有一些偏移量,因為另一側很可能在檔案的較早位置插入或刪除過行。上下文區域旨在幫助尋找如何調整行號,以便將更改套用到正確的行。然而,如果程式碼的多個區域具有相同的上下文行,則可能會選錯。在現實案例中,這曾導致提交被錯誤套用且未報告任何衝突。將 diff.context 設定為較大的值可能會防止此類問題,但會增加發生虛假衝突的機會(因為套用更改需要更多匹配的上下文行)。

merge 後端使用每個相關檔案的完整副本進行工作,使其免受此類問題的影響。

衝突標記的標籤

當發生內容衝突時,合併機制會嘗試使用內容來源的提交來標註每一側的衝突標記。由於 apply 後端丟棄了關於重基提交及其父提交的原始資訊(而是根據產生的補丁中的有限資訊產生新的虛假提交),因此無法識別這些提交;相反,它必須退而使用提交摘要。此外,當 merge.conflictStyle 設定為 diff3zdiff3 時,apply 後端將使用「建構的合併基底」來標註來自合併基底的內容,因此完全不提供關於合併基底提交的資訊。

merge 後端使用歷史兩側的完整提交,因此沒有此類限制。

掛鉤 (Hooks)

傳統上,apply 後端不會呼叫 post-commit 掛鉤,而 merge 後端會。兩者都會呼叫 post-checkout 掛鉤,儘管 merge 後端會抑制其輸出。此外,兩個後端都只會在重基的起點提交時呼叫 post-checkout 掛鉤,而不會在中間提交或最終提交時呼叫。在每種情況下,呼叫這些掛鉤都是實作過程中的意外,而非設計使然(兩個後端最初都是作為 shell 指令碼實作的,剛好呼叫了會觸發掛鉤的其他命令,如 git checkoutgit commit)。兩個後端的行為應該一致,儘管目前還不清楚哪一個(如果有)是正確的。我們未來可能會讓 rebase 停止呼叫這兩個掛鉤中的任何一個。

可中斷性

apply 後端在不合時宜的中斷下存在安全問題;如果使用者在錯誤的時間按下 Ctrl-C 嘗試中止重基,重基可能會進入一個無法使用後續 git rebase --abort 中止的狀態。merge 後端似乎不存在同樣的缺點。(詳情請參閱 https://lore.kernel.org/git/20200207132152.GC2868@szeder.dev/。)

修改提交訊息 (Commit Rewording)

重基過程中發生衝突時,重基會停止並要求使用者解決。由於使用者在解決衝突時可能需要做出顯著更改,因此在衝突解決且使用者執行 git rebase --continue 後,重基應開啟編輯器並要求使用者更新提交訊息。merge 後端會這樣做,而 apply 後端則盲目地套用原始提交訊息。

其他差異

還有一些行為差異,大多數人可能認為無關緊要,但為了完整起見仍予以列出:

  • 參照日誌 (Reflog):兩個後端在描述參照日誌中所做的更改時會使用不同的字眼,儘管兩者都會使用「rebase」一詞。

  • 進度、資訊和錯誤訊息:兩個後端提供的進度和資訊訊息略有不同。此外,apply 後端將錯誤訊息(例如 "Your files would be overwritten…​")寫入 stdout,而 merge 後端將其寫入 stderr。

  • 狀態目錄:兩個後端將其狀態儲存在 .git/ 下不同的目錄中。

合併策略

合併機制(git mergegit pull 命令)允許透過 -s 選項選擇後端 合併策略。某些策略還可以接受自己的選項,這些選項可以透過向 git merge 和/或 git pull 提供 -X<option> 參數來傳遞。

ort

這是拉取 (pull) 或合併單一分支時的預設合併策略。此策略僅能使用三方合併演算法解決兩個分支頭 (heads)。當有多個可用於三方合併的共同祖先時,它會建立一個共同祖先的合併樹,並將其用作三方合併的參考樹。根據在 Linux 2.6 核心開發歷史中的實際合併提交進行的測試,據報此方法產生的合併衝突較少,且不會導致錯誤合併。此外,此策略可以偵測並處理涉及重新命名的合併。它不使用偵測到的複製。此演算法的名稱是一個縮寫(「Ostensibly Recursive’s Twin」,意為表面上是遞迴策略的孿生兄弟),因為它是作為先前預設演算法 recursive 的替代方案而編寫的。

在路徑為子模組的情況下,如果合併的一側所使用的子模組提交是另一側所使用提交的後代,Git 會嘗試快速前進到該後代。否則,Git 會將此情況視為衝突,並建議使用一個同時是衝突提交之後代的子模組提交(如果存在)作為解決方案。

ort 策略可以接受以下選項:

ours

此選項強制偏向 our (我方) 版本以自動乾淨地解決衝突區塊。來自另一棵樹但不與我方衝突的更改會反映在合併結果中。對於二進位檔案,整個內容都取自我方。

這不應與 ours 合併策略混淆,後者甚至根本不查看另一棵樹的內容。它會丟棄另一棵樹所做的所有事情,宣稱 our 歷史已包含其中發生的所有事情。

theirs

這與 ours 相對;請注意,與 ours 不同,沒有 theirs 合併策略會與此合併選項混淆。

ignore-space-change
ignore-all-space
ignore-space-at-eol
ignore-cr-at-eol

為了進行三方合併,將具有指定類型空白變更的行視為未更改。混合有其他更改的空白變更不會被忽略。另請參閱 git-diff[1] -b-w--ignore-space-at-eol--ignore-cr-at-eol

  • 如果 their 版本僅在行中引入空白變更,則使用 our 版本;

  • 如果 our 版本引入空白變更,但 their 版本包含實質性更改,則使用 their 版本;

  • 否則,合併將以通常的方式進行。

renormalize

這會對需要三方合併的任何檔案的所有三個階段進行虛擬檢出和檢入。此選項旨在於合併具有不同 clean 過濾器或行尾標準化規則的分支時使用。詳情請參閱 gitattributes[5] 中的「合併具有不同檢入/檢出屬性的分支」。

no-renormalize

停用 renormalize 選項。這會覆蓋 merge.renormalize 設定變數。

find-renames[=<n>]

開啟重新命名偵測,並可選擇設定相似度閾值。這是預設行為。這會覆蓋 merge.renames 設定變數。另請參閱 git-diff[1] --find-renames

rename-threshold=<n>

find-renames=<n> 的已棄用同義詞。

no-renames

關閉重新命名偵測。這會覆蓋 merge.renames 設定變數。另請參閱 git-diff[1] --no-renames

histogram

diff-algorithm=histogram 的已棄用同義詞。

patience

diff-algorithm=patience 的已棄用同義詞。

diff-algorithm=(histogram|minimal|myers|patience)

合併時使用不同的差異演算法,這有助於避免因不重要的匹配行(如來自不同函式的大括號)而發生的錯誤合併。另請參閱 git-diff[1] --diff-algorithm。請注意,ort 預設為 diff-algorithm=histogram,而一般的差異比較目前預設為 diff.algorithm 設定。

subtree[=<path>]

此選項是 subtree 策略的一種進階形式,該策略會猜測合併時兩棵樹必須如何移動才能相互匹配。相反,指定的路徑會被加上前綴(或從開頭去除),以使兩棵樹的形狀匹配。

recursive

這現在是 ort 的同義詞。在 v2.49.0 之前,它是一個替代實作,但在 v2.50.0 中被重新導向為 ort。先前的遞迴 (recursive) 策略是從 Git v0.99.9k 到 v2.33.0 解決兩個分支頭的預設策略。

resolve

此策略僅能使用三方合併演算法解決兩個分支頭(即目前分支和您拉取的另一個分支)。它嘗試仔細偵測交叉合併的歧義。它不處理重新命名。

octopus

這可以解決兩個以上分支頭的情況,但拒絕執行需要手動解決的複雜合併。它主要用於將多個主題分支頭捆綁在一起。這是拉取或合併多個分支時的預設合併策略。

ours

這可以解決任意數量的分支頭,但合併後產生的樹始終是目前分支頭的樹,實際上忽略了來自所有其他分支的所有更改。它旨在用於取代舊的側支開發歷史。請注意,這與 ort 合併策略的 -Xours 選項不同。

subtree

這是一個修改後的 ort 策略。合併樹 A 和 B 時,如果 B 對應於 A 的子樹,則首先調整 B 以匹配 A 的樹結構,而不是在同一層級讀取樹。這種調整也會對共同祖先樹進行。

對於使用三方合併的策略(包括預設的 ort),如果在兩個分支上都做了更改,但後來在其中一個分支上還原了該更改,則合併結果中將存在該更改;有些人覺得這種行為很令人困惑。這是因為執行合併時只考慮分支頭和合併基底,而不考慮單個提交。因此,合併演算法將還原的更改視為完全沒有更改,並代之以更改後的版本。

注意事項

您應該了解在共享的儲存庫上使用 git rebase 的後果。另請參閱下方的「從上游重基中恢復」。

執行重基時,如果存在 pre-rebase 掛鉤,它會首先執行。您可以使用此掛鉤進行健全性檢查,如果重基不合適,則拒絕重基。請參閱 pre-rebase 掛鉤範本指令碼以獲取範例。

完成後,<branch> 將成為當前分支。

互動模式

以互動方式重基意味著您有機會編輯正在重基的提交。您可以對提交重新排序,也可以刪除提交(剔除錯誤或不需要的補丁)。

互動模式適用於此類工作流程:

  1. 有一個很棒的想法

  2. 編寫程式碼

  3. 準備提交的系列作品

  4. 提交

其中第 2 點包含以下幾種情況:

a) 常規使用

  1. 完成值得提交的工作

  2. commit

b) 獨立修復

  1. 意識到某些東西不起作用

  2. 修復它

  3. 提交它

有時 b.2. 中修復的內容無法修改到它所修復的那個不完美的提交中,因為該提交深埋在補丁系列中。這正是互動式重基的用途:在大量 "a" 和 "b" 之後使用它,透過重新排列和編輯提交,並將多個提交壓縮為一個。

從您想要原樣保留的最後一個提交開始啟動:

git rebase -i <after-this-commit>

系統將啟動一個編輯器,列出當前分支中給定提交之後的所有提交(忽略合併提交)。您可以隨意重新排序此列表中的提交,也可以刪除它們。列表看起來大概像這樣:

pick deadbee The oneline of this commit
pick fa1afe1 The oneline of the next commit
...

單行描述純粹是為了方便您閱讀;git rebase 不會看它們,而是看提交名稱(在此範例中為 "deadbee" 和 "fa1afe1"),因此請勿刪除或編輯名稱。

透過將 "pick" 指令替換為 "edit" 指令,您可以告訴 git rebase 在套用該提交後停止,以便您可以編輯檔案和/或提交訊息、修改 (amend) 提交並繼續重基。

要中斷重基(就像 "edit" 指令會做的那樣,但不先挑選任何提交),請使用 "break" 指令。

如果您只想編輯某個提交的提交訊息,請將 "pick" 指令替換為 "reword" 指令。

要丟棄一個提交,請將 "pick" 指令替換為 "drop",或直接刪除該行。

如果您想將兩個或多個提交合併為一個,請將第二個及後續提交的 "pick" 指令替換為 "squash" 或 "fixup"。如果提交有不同的作者,則合併後的提交將歸功於第一個提交的作者。建議的合併提交訊息是第一個提交的訊息與 "squash" 指令標識的訊息的串接,省略 "fixup" 指令標識的提交訊息,除非使用了 "fixup -c"。在這種情況下,建議的提交訊息僅為 "fixup -c" 提交的訊息,並且會開啟編輯器允許您編輯該訊息。"fixup -c" 提交的內容(補丁)仍會併入合併後的提交中。如果有多個 "fixup -c" 提交,則使用最後一個提交的訊息。您也可以使用 "fixup -C" 來獲得與 "fixup -c" 相同的行為,但不開啟編輯器。

當 "pick" 被替換為 "edit" 或命令因合併錯誤而失敗時,git rebase 將停止。完成編輯和/或解決衝突後,您可以使用 git rebase --continue 繼續。

例如,如果您想對最後 5 個提交重新排序,使原來的 HEAD~4 成為新的 HEAD。為了實現這一點,您可以這樣呼叫 git rebase

$ git rebase -i HEAD~5

然後將第一個補丁移至列表末尾。

您可能想要重新建立合併提交,例如如果您有這樣的歷史:

           X
            \
         A---M---B
        /
---o---O---P---Q

假設您想將從 "A" 開始的側支重基到 "Q"。確保當前 HEAD 為 "B",然後呼叫:

$ git rebase -i -r --onto Q O

重新排序和編輯提交通常會產生未經測試的中間步驟。您可能想透過執行測試來檢查歷史編輯是否破壞了任何東西,或者至少透過使用 "exec" 指令(簡寫為 "x")在歷史的中間點重新編譯。您可以透過建立如下所示的待辦列表來做到這一點:

pick deadbee Implement feature XXX
fixup f1a5c00 Fix to feature XXX
exec make
pick c0ffeee The oneline of the next commit
edit deadbab The oneline of the commit after
exec cd subdir; make test
...

當命令失敗(即以非 0 狀態退出)時,互動式重基將停止,給您修復問題的機會。您可以使用 git rebase --continue 繼續。

"exec" 指令在 shell(預設 shell,通常是 /bin/sh)中啟動命令,因此您可以使用 shell 功能(如 "cd"、">"、";" …​)。該命令在工作區的根目錄下執行。

$ git rebase -i --exec "make test"

此指令可讓您檢查中間提交是否可編譯。待辦列表會變成這樣:

pick 5928aea one
exec make test
pick 04d0fda two
exec make test
pick ba46169 three
exec make test
pick f4593f9 four
exec make test

分割提交

在互動模式下,您可以用 "edit" 操作標記提交。然而,這並不一定意味著 git rebase 期望這次編輯的結果恰好是一個提交。事實上,您可以撤銷該提交,或者新增其他提交。這可以用來將一個提交分割成兩個:

  • 使用 git rebase -i <commit>^ 啟動互動式重基,其中 <commit> 是您想要分割的提交。事實上,任何包含該提交的提交範圍都可以。

  • 用 "edit" 操作標記您想要分割的提交。

  • 當執行到編輯該提交時,執行 git reset HEAD^。效果是 HEAD 倒回一格,索引也隨之倒回。但是,工作區保持不變。

  • 現在將您想要放在第一個提交中的更改新增到索引中。您可以使用 git add(可能以互動方式)或 git gui(或兩者)來完成此操作。

  • 使用目前合適的提交訊息提交當前索引。

  • 重複最後兩個步驟,直到您的工作區變乾淨。

  • 使用 git rebase --continue 繼續重基。

如果您不絕對確定中間修訂版本是一致的(它們可以編譯、通過測試套件等),您應該使用 git stash 在每次提交、測試後儲藏尚未提交的更改,並在需要修復時修改提交。

從上游重基中恢復

在他人已基於其開展工作之分支上進行重基(或任何其他形式的重寫)是一個壞主意:其下游的任何人都被迫手動修復其歷史記錄。本節說明如何從下游的角度進行修復。然而,真正的修復方法應該是從一開始就避免重基上游。

為了說明,假設您處於有人開發 subsystem(子系統)分支,而您正在開發依賴於此 subsystemtopic(主題)分支的情況。您最終可能會得到如下所示的歷史:

    o---o---o---o---o---o---o---o  master
	 \
	  o---o---o---o---o  subsystem
			   \
			    *---*---*  topic

如果 subsystem 針對 master 進行了重基,會發生以下情況:

    o---o---o---o---o---o---o---o  master
	 \			 \
	  o---o---o---o---o	  o'--o'--o'--o'--o'  subsystem
			   \
			    *---*---*  topic

如果您現在照常繼續開發,並最終將 topic 合併到 subsystem,那麼來自 subsystem 的提交將永遠重複存在:

    o---o---o---o---o---o---o---o  master
	 \			 \
	  o---o---o---o---o	  o'--o'--o'--o'--o'--M	 subsystem
			   \			     /
			    *---*---*-..........-*--*  topic

此類重複提交通常是不受歡迎的,因為它們會使歷史變得混亂,難以追蹤。為了清理歷史,您需要將 topic 上的提交移植到新的 subsystem 頂端,即重基 topic。這會產生漣漪效應:topic 下游的任何人都被迫也要重基,依此類推!

修復方法有兩種,將在以下小節中討論:

簡單情況:更改內容完全相同。

如果 subsystem 的重基是簡單的重基且沒有衝突,就會發生這種情況。

困難情況:更改內容不相同。

如果 subsystem 重基有衝突,或者使用了 --interactive 來省略、編輯、壓縮或修正提交;或者上游使用了 commit --amendreset 或像 filter-repo 這樣的完整歷史重寫命令,就會發生這種情況。

簡單情況

僅當 subsystem 上的更改(基於 diff 內容的補丁 ID)在 subsystem 執行重基前後完全相同時才有效。

在這種情況下,修復很容易,因為 git rebase 知道跳過新上游中已存在的更改(除非給出了 --reapply-cherry-picks)。因此,如果您執行(假設您在 topic 上):

    $ git rebase subsystem

您將得到修復後的歷史:

    o---o---o---o---o---o---o---o  master
				 \
				  o'--o'--o'--o'--o'  subsystem
						   \
						    *---*---*  topic

困難情況

如果 subsystem 的變更與重基前的變更不完全對應,情況就會變得更加複雜。

注意
雖然「簡單情況恢復」有時即使在困難情況下似乎也能成功,但它可能會產生意想不到的後果。例如,透過 git rebase --interactive 移除的提交將會復活

這個想法是手動告訴 git rebase 「舊的 subsystem 在哪裡結束,您的 topic 在哪裡開始」,也就是說,它們之間舊的合併基底是什麼。您必須找到一種方法來命名舊 subsystem 的最後一個提交,例如:

  • 使用 subsystem 的參照日誌 (reflog):在 git fetch 之後,subsystem 的舊頂端位於 subsystem@{1}。隨後的獲取會增加此編號。(見 git-reflog[1]。)

  • 相對於 topic 的頂端:已知您的 topic 有三個提交,則 subsystem 的舊頂端必須是 topic~3

然後您可以透過執行以下命令將舊的 subsystem..topic 移植到新頂端(以參照日誌為例,並假設您已經在 topic 上):

    $ git rebase --onto subsystem subsystem@{1}

「困難情況」恢復的漣漪效應尤其嚴重:topic 下游的「每個人」現在也必須執行「困難情況」恢復!

重基合併

互動式重基命令最初旨在處理單個補丁系列。因此,從待辦列表中排除合併提交是有意義的,因為開發人員可能在分支上工作時合併了當時的 master,最終只是為了將所有提交重基到 master 上(跳過合併提交)。

然而,開發人員想要重新建立合併提交是有正當理由的:在處理多個相互關聯的分支時保持分支結構(或「提交拓撲」)。

在以下範例中,開發人員在一個主題分支上重構按鈕定義的方式,並在另一個主題分支上使用該重構來實作「報告錯誤」按鈕。git log --graph --format=%s -5 的輸出可能如下所示:

*   Merge branch 'report-a-bug'
|\
| * Add the feedback button
* | Merge branch 'refactor-button'
|\ \
| |/
| * Use the Button class for all buttons
| * Extract a generic Button class from the DownloadButton one

開發人員可能希望將這些提交重基到較新的 master,同時保留分支拓撲,例如當預計第一個主題分支比第二個更早整合到 master 時,例如為了解決與進入 master 的 DownloadButton 類別更改之間的合併衝突。

此重基可以使用 --rebase-merges 選項執行。它將產生如下所示的待辦列表:

label onto

# Branch: refactor-button
reset onto
pick 123456 Extract a generic Button class from the DownloadButton one
pick 654321 Use the Button class for all buttons
label refactor-button

# Branch: report-a-bug
reset refactor-button # Use the Button class for all buttons
pick abcdef Add the feedback button
label report-a-bug

reset onto
merge -C a1b2c3 refactor-button # Merge 'refactor-button'
merge -C 6f5e4d report-a-bug # Merge 'report-a-bug'

與常規的互動式重基相比,除了 pick 指令外,還有 labelresetmerge 指令。

label 指令在執行時會將標籤與目前的 HEAD 關聯。這些標籤被建立為工作樹區域引用 (refs/rewritten/<label>),並在變基(rebase)結束時刪除。如此一來,在連結到同一個儲存庫的多個工作樹中進行的變基操作就不會互相干擾。如果 label 指令失敗,它會立即重新排程,並提供如何繼續操作的實用訊息。

reset 指令會將 HEAD、索引(index)和工作樹重設為指定的修訂版本。它類似於 exec git reset --hard <label>,但拒絕覆蓋未追蹤的檔案。如果 reset 指令失敗,它會立即重新排程,並提供如何編輯待辦清單的實用訊息(這通常發生在手動將 reset 指令插入待辦清單且包含打字錯誤時)。

merge 指令會將指定的修訂版本合併到當時的 HEAD 中。使用 -C <original-commit> 時,會使用指定合併提交的提交訊息。當 -C 改為小寫的 -c 時,合併成功後會在編輯器中開啟該訊息,以便使用者編輯訊息。

如果 merge 指令因合併衝突以外的任何原因失敗(即合併操作甚至尚未開始),它會立即重新排程。

預設情況下,merge 指令對一般合併會使用 ort 合併策略,而對多路合併(octopus merges)則使用 octopus。使用者可以在呼叫 rebase 時使用 --strategy 參數為所有合併指定預設策略,或者可以在互動式指令清單中使用 exec 指令明確呼叫帶有 --strategy 參數的 git merge 來覆蓋特定的合併。請注意,當像這樣明確呼叫 git merge 時,您可以利用標籤是工作樹區域引用(例如:引用 refs/rewritten/onto 會對應到標籤 onto)這一事實,來引用您想要合併的分支。

注意:第一個指令 (label onto) 會標記提交要變基到的目標修訂版本;onto 這個名稱只是一個慣例,是為了呼應 --onto 選項。

也可以透過加入格式為 merge <merge-head> 的指令,從頭開始引入全新的合併提交。這種形式會產生一個暫定的提交訊息,並始終開啟編輯器讓使用者進行編輯。這在某些情況下很有用,例如當一個主題分支(topic branch)被發現處理了不只一個問題,並希望拆分為兩個甚至更多主題分支時。請考慮這個待辦清單

pick 192837 Switch from GNU Makefiles to CMake
pick 5a6c7e Document the switch to CMake
pick 918273 Fix detection of OpenSSL in CMake
pick afbecd http: add support for TLS v1.3
pick fdbaec Fix detection of cURL in CMake on Windows

此清單中唯一與 CMake 無關的提交,很可能是因為在修復由切換到 CMake 所引入的所有錯誤時產生的動機,但它處理的是不同的問題。為了將此分支拆分為兩個主題分支,可以像這樣編輯待辦清單

label onto

pick afbecd http: add support for TLS v1.3
label tlsv1.3

reset onto
pick 192837 Switch from GNU Makefiles to CMake
pick 918273 Fix detection of OpenSSL in CMake
pick fdbaec Fix detection of cURL in CMake on Windows
pick 5a6c7e Document the switch to CMake
label cmake

reset onto
merge tlsv1.3
merge cmake

組態設定 (CONFIGURATION)

本節中此行以下的內容是從 git-config[1] 文件中選擇性包含的。內容與該處找到的內容相同

rebase.backend

變基時使用的預設後端。可選項目為 applymerge。在未來,如果 merge 後端獲得了 apply 後端的所有剩餘功能,此設定可能會變得不再使用。

rebase.stat

是否顯示自上次變基以來上游變化的差異統計(diffstat)。預設為 false。

rebase.autoSquash

如果設為 true,則在互動模式下預設啟用 git-rebase[1]--autosquash 選項。這可以使用 --no-autosquash 選項來覆蓋。

rebase.autoStash

當設為 true 時,在操作開始前自動建立一個臨時暫存(stash)條目,並在操作結束後套用它。這意味著您可以在工作樹有未提交變更(dirty worktree)的情況下執行變基。但是,請謹慎使用:變基成功後的最終暫存套用可能會導致非簡單的衝突。此選項可以被 git-rebase[1]--no-autostash--autostash 選項覆蓋。預設為 false。

rebase.updateRefs

如果設為 true,預設啟用 --update-refs 選項。

rebase.missingCommitsCheck

如果設為 "warn",當某些提交被移除時(例如刪除了一行),git rebase -i 將印出警告,但變基仍會繼續進行。如果設為 "error",它將印出前述警告並停止變基,接著可以使用 git rebase --edit-todo 來修正錯誤。如果設為 "ignore",則不進行任何檢查。若要捨棄提交而不產生警告或錯誤,請在待辦清單中使用 drop 指令。預設為 "ignore"。

rebase.instructionFormat

git-log[1] 中所指定的格式字串,用於互動式變基期間的待辦清單。該格式會自動在前面加上提交雜湊(commit hash)。

rebase.abbreviateCommands

如果設為 true,git rebase 將在待辦清單中使用縮寫的指令名稱,結果如下所示

	p deadbee The oneline of the commit
	p fa1afe1 The oneline of the next commit
	...

而不是

	pick deadbee The oneline of the commit
	pick fa1afe1 The oneline of the next commit
	...

預設為 false。

rebase.rescheduleFailedExec

自動重新排程失敗的 exec 指令。這僅在互動模式(或提供了 --exec 選項時)有意義。這與指定 --reschedule-failed-exec 選項相同。

rebase.forkPoint

如果設為 false,則預設設定 --no-fork-point 選項。

rebase.rebaseMerges

是否以及如何預設設定 --rebase-merges 選項。可以是 rebase-cousinsno-rebase-cousins 或布林值。設為 true 或 no-rebase-cousins 等同於 --rebase-merges=no-rebase-cousins,設為 rebase-cousins 等同於 --rebase-merges=rebase-cousins,而設為 false 等同於 --no-rebase-merges。在命令列傳遞 --rebase-merges(不論是否帶有參數)都會覆蓋任何 rebase.rebaseMerges 設定。

rebase.maxLabelLength

從提交主旨產生標籤名稱時,將名稱截斷至此長度。預設情況下,名稱會截斷為略小於 NAME_MAX 的長度(以便為對應的鬆散引用寫入例如 .lock 檔案)。

sequence.editor

git rebase -i 用於編輯變基指令檔案的文字編輯器。該值在使用時應由 shell 解釋。它可以被 GIT_SEQUENCE_EDITOR 環境變數覆蓋。若未設定,則改用預設的提交訊息編輯器。

GIT

git[1] 套件的一部分