简体中文 ▾ 主题 ▾ 最新版本 ▾ git-rebase 最后更新于 2.53.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

你想将你在 topic 分支自偏离 master 以来所做的提交(即 A、B 和 C)移植到当前 master 的顶部。你可以在检出 topic 分支时运行 git rebase master 来实现。如果你想在其他分支上变基 topicgit 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 <文件名> 将冲突标记为已解决。解决所有冲突后,你可以继续变基过程:

    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

我们想让 topic 改为从 master 分支派生;例如,因为 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

通过跳过当前补丁重新启动变基过程。

--abort

中止变基操作并将 HEAD 重置为原始分支。如果在启动变基操作时提供了 <branch>,则 HEAD 将重置为 <branch>。否则,HEAD 将重置为启动变基操作时的位置。

--quit

中止变基操作,但不将 HEAD 重置回原始分支。索引和工作树也保持不变。如果使用 --autostash 创建了临时暂存条目,它将被保存到暂存列表中。

--edit-todo

在交互式变基期间编辑待办事项列表(todo list)。

--show-current-patch

在交互式变基中,或当变基由于冲突停止时,显示当前补丁。这等同于 git show REBASE_HEAD

选项

--onto <newbase>

创建新提交的起点。如果未指定 --onto 选项,则起点为 <upstream>。可以是任何有效的提交,而不只是现有的分支名称。

作为特殊情况,如果 A 和 B 之间恰好有一个合并基准,你可以使用 "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

使用应用策略(内部调用 git-am)进行变基。将来一旦合并后端能处理应用后端所做的一切,此选项可能会变成无操作(no-op)。

另请参阅下文的“不兼容选项”。

--empty=(drop|keep|stop)

如何处理那些开始时不为空且不是对任何上游提交的干净拣选(cherry-pick),但在变基后变为空的提交(因为它们包含了上游已有的变更子集):

drop

提交将被丢弃。这是默认行为。

keep

提交将被保留。当指定了 --exec 且未指定 -i/--interactive 时,隐含此选项。

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=<策略>

使用给定的合并策略,而不是默认的 ort。这隐含了 --merge

由于 git rebase 使用给定策略在 <upstream> 分支之上重放工作分支的每个提交,因此使用 ours 策略只会清空来自 <branch> 的所有补丁,这几乎没有意义。

另请参阅下文的“不兼容选项”。

-X <策略选项>
--strategy-option=<策略选项>

将 <策略选项> 传递给合并策略。这隐含了 --merge,且如果未指定策略,则隐含 -s ort。注意如上所述 -m 选项中 ourstheirs 的反转。

另请参阅下文的“不兼容选项”。

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

在 rerere 机制使用记录的冲突解决来更新工作树中的文件后,允许它也用解决结果更新索引。--no-rerere-autoupdate 是一个很好的方式来双重检查 rerere 所做的操作,并在使用单独的 git add 将结果提交到索引之前,捕获潜在的错误合并。

-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 How-To)。

--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> 发生了回滚,且你的分支包含已被丢弃的提交,则此选项可以与 --keep-base 结合使用,以从你的分支中丢弃这些提交。

另请参阅下文的“不兼容选项”。

--ignore-whitespace

在尝试协调差异时忽略空白差异。目前,每个后端都实现了这种行为的近似:

apply 后端

应用补丁时,忽略上下文行中的空白变化。不幸的是,这意味着如果补丁替换的“旧”行与现有文件仅在空白方面有所不同,你将得到合并冲突,而不是成功的补丁应用。

merge 后端

合并时将仅有空白变化的行视为未更改。不幸的是,这意味着任何旨在修改空白且不执行其他操作的补丁块都将被丢弃,即使另一侧没有冲突的更改。

--whitespace=<选项>

此标志被传递给应用补丁的 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 的提交才会添加尾注。

另请参阅下文的“不兼容选项”。

-i
--interactive

列出即将变基的提交。允许用户在变基前编辑该列表。此模式也可用于拆分提交(见下文“拆分提交”)。

提交列表格式可以通过设置配置选项 rebase.instructionFormat 来更改。自定义的指令格式会自动在格式前加上提交哈希。

另请参阅下文的“不兼容选项”。

-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 <策略> [...] 命令使用。

另见下文的“变基合并提交”和“不兼容选项”。

-x <命令>
--exec <命令>

在最终历史中创建提交的每一行之后附加 "exec <命令>"。<命令> 将被解释为一个或多个 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> 限制它们。这允许你变基分支上的根提交。

另请参阅下文的“不兼容选项”。

--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

在操作开始前自动创建一个临时暂存条目,并在操作结束后应用它。这意味着你可以在脏工作树上运行变基。但是,请谨慎使用:成功变基后的最终暂存应用可能会导致复杂的冲突。

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

自动重新调度失败的 exec 命令。这仅在交互模式(或提供了 --exec 选项时)有意义。

此选项在变基开始后生效。它在整个变基过程中按顺序基于:提供给初始 git rebase 的命令行选项、rebase.rescheduleFailedExec 配置(见 git-config[1] 或下文的“配置”),否则默认为 false。

在整个变基过程中记录此选项是一项便利功能。否则,在调用 git rebase --continue 时,初始时显式的 --no-reschedule-failed-exec 会被 rebase.rescheduleFailedExec=true 配置的存在所覆盖。目前,你不能将 --[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=

  • 在不使用 --keep-base 时使用的 --[no-]reapply-cherry-picks

  • --update-refs

  • 在不使用 --onto 时使用的 --root

此外,以下成对选项不兼容:

  • --keep-base 和 --onto

  • --keep-base 和 --root

  • --fork-point 和 --root

行为差异

git rebase 有两个主要的后端:applymerge。(apply 后端以前被称为 am 后端,但该名称容易引起混淆,因为它看起来像动词而不是名词。此外,merge 后端以前被称为交互式后端,但现在它也用于非交互式情况。两者都根据支撑各自的底层功能进行了重命名。)这两个后端的行为方式有一些细微的差异:

空提交

不幸的是,apply 后端会丢弃有意创建的空提交(即原本就为空的提交),尽管这在实践中很少见。它还会丢弃变为空的提交,并且没有控制此行为的选项。

merge 后端默认保留有意创建的空提交(尽管使用 -i 时,它们在待办事项列表编辑器中会被标记为空,或者可以使用 --no-keep-empty 自动丢弃)。

与 apply 后端类似,默认情况下 merge 后端会丢弃变为空的提交,除非指定了 -i/--interactive(在这种情况下,它会停止并询问用户如何操作)。merge 后端还有一个 --empty=(drop|keep|stop) 选项,用于更改处理变为空提交的行为。

目录重命名检测

由于缺乏准确的树信息(源于使用补丁中可用的有限信息构建虚假祖先),apply 后端禁用了目录重命名检测。禁用目录重命名检测意味着如果历史的一侧重命名了目录,而另一侧向旧目录添加了新文件,那么新文件将留在旧目录中,在变基时不会发出任何可能需要将这些文件移入新目录的警告。

目录重命名检测在 merge 后端中有效,可以为此类情况提供警告。

上下文

apply 后端的工作方式是创建一个补丁序列(通过内部调用 format-patch),然后按顺序应用补丁(内部调用 am)。补丁由多个数据块(hunk)组成,每个块都有行号、上下文区域和实际更改。行号必须带有一定的偏移量,因为另一侧很可能在文件的较早位置插入或删除了行。上下文区域旨在帮助找到如何调整行号,以便将更改应用到正确的行。但是,如果代码的多个区域具有相同的周围上下文行,可能会选错区域。在现实世界中,曾出现过导致提交被错误地重新应用且未报告冲突的情况。将 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/。)

提交消息改写

变基期间发生冲突时,变基会停止并要求用户解决。由于用户在解决冲突时可能需要做出显著更改,因此在冲突解决且用户运行 git rebase --continue 后,变基应打开编辑器并要求用户更新提交消息。merge 后端会这样做,而 apply 后端则盲目地应用原始提交消息。

其他差异

还有一些行为差异,大多数人可能会认为无关紧要,但为了完整起见还是提一下:

  • Reflog:两个后端在描述 reflog 中的更改时会使用不同的措辞,尽管两者都会使用 "rebase" 一词。

  • 进度、信息和错误消息:两个后端提供的进度和信息消息略有不同。此外,apply 后端将错误消息(例如 "Your files would be overwritten…")写入 stdout,而 merge 后端将其写入 stderr。

  • 状态目录:两个后端将它们的状态保存在 .git/ 下的不同目录中。

合并策略

合并机制(git mergegit pull 命令)允许使用 -s 选项选择后端合并策略。一些策略还可以接受自己的选项,这些选项可以通过向 git merge 和/或 git pull 传递 -X<option> 参数来传递。

ort

这是拉取或合并一个分支时的默认合并策略。该策略只能使用三向合并算法来解析两个头。当存在多个可用于三向合并的共同祖先时,它会创建一个共同祖先的合并树,并将其用作三向合并的参考树。根据从 Linux 2.6 内核开发历史中提取的实际合并提交进行的测试,这可以减少合并冲突,而不会导致错误合并。此外,该策略可以检测和处理涉及重命名的合并。它不利用检测到的复制。该算法的名称是一个首字母缩略词(“Ostensibly Recursive’s Twin”),源于它是作为先前默认算法 recursive 的替代品而编写的。

在路径是子模块的情况下,如果合并一方使用的子模块提交是合并另一方使用的子模块提交的后代,Git 会尝试快进到后代。否则,Git 会将这种情况视为冲突,并建议一个与冲突的子模块提交的后代作为解决方案(如果存在)。

ort 策略可以接受以下选项:

ours

此选项强制冲突的块自动干净地解决,优先采用我们的版本。来自另一棵树但与我们这方没有冲突的更改会反映在合并结果中。对于二进制文件,整个内容将来自我们这边。

这不应与 ours 合并策略混淆,后者根本不查看另一棵树包含的内容。它丢弃了另一棵树所做的所有更改,声称我们的历史包含了发生的所有事件。

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

  • 如果*他们的*版本只引入了行的空白符更改,则使用*我们的*版本;

  • 如果*我们的*版本引入了空白符更改但*他们的*版本包含了实质性更改,则使用*他们的*版本;

  • 否则,合并按常规方式进行。

renormalize

这将对任何需要三向合并的文件执行虚拟签出和签入所有三个阶段。此选项用于合并具有不同签入/签出属性的分支。有关详细信息,请参阅 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)

在合并时使用不同的 diff 算法,这有助于避免由于不重要的匹配行(例如来自不同函数的括号)而导致的错误合并。另请参阅 git-diff[1]--diff-algorithm。请注意,ort 默认为 diff-algorithm=histogram,而常规 diff 目前默认为 diff.algorithm 配置设置。

subtree[=<path>]

此选项是子目录策略的高级形式,在该策略中,合并时策略会猜测两个树必须如何偏移才能匹配。相反,指定的路径会作为前缀(或从开头剥离)以使两个树的形状匹配。

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. 1. 有一个绝妙的主意

  2. 2. 编写代码(hack)

  3. 3. 准备一系列提交以供提交

  4. 4. 提交

其中第 2 点包含以下情况的多次实例:

a) 常规使用

  1. 1. 完成值得提交的工作

  2. commit

b) 独立修复

  1. 1. 意识到某些东西不起作用

  2. 2. 修复它

  3. 3. 提交它

有时在 b.2 中修复的内容无法直接通过 amend 合并到它所修复的不太完美的提交中,因为该提交深埋在补丁系列中。这正是交互式变基的用途:在进行大量 "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 在应用该提交后停止,以便你可以编辑文件和/或提交消息,修改提交,然后继续变基。

要中断变基(就像 "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(默认为 /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 变基前后的更改(基于差异内容的补丁 ID)完全相同时才有效。

在这种情况下,修复很容易,因为 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}。随后的 fetch 会增加这个数字。(见 git-reflog[1]。)

  • 相对于 topic 的末端:已知你的 topic 有三个提交,旧 subsystem 的末端必然是 topic~3

然后,你可以通过执行以下命令(以 reflog 情况为例,并假设你已经在 topic 上)将旧的 subsystem..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>)创建,并在变基完成时删除。这样,链接到同一仓库的多个工作树中的变基操作就不会相互干扰。如果 label 命令失败,它会立即重新调度,并显示有关如何继续的有用消息。

reset 命令将 HEAD、索引和工作树重置为指定的修订版本。它类似于 exec git reset --hard <label>,但拒绝覆盖未跟踪的文件。如果 reset 命令失败,它会立即重新调度,并显示有关如何编辑待办事项列表的有用消息(这通常发生在手动在待办事项列表中插入了包含拼写错误的 reset 命令时)。

merge 命令将指定的修订版本合并到当时的 HEAD 中。配合 -C <original-commit>,将使用指定合并提交的提交消息。当 -C 更改为小写 -c 时,合并成功后将在编辑器中打开消息,以便用户编辑。

如果 merge 命令由于合并冲突以外的任何原因失败(即合并操作甚至没有开始),它会立即重新调度。

默认情况下,merge 命令将对常规合并使用 ort 合并策略,对章鱼合并(octopus merge)使用 octopus 策略。调用 rebase 时可以使用 --strategy 参数为所有合并指定默认策略,也可以在交互式命令列表中通过使用 exec 命令显式调用带有 --strategy 参数的 git merge 来覆盖特定合并。请注意,当像这样显式调用 git merge 时,你可以利用标签是工作树本地引用这一事实(例如,引用 refs/rewritten/onto 将对应于标签 onto),以便引用你想合并的分支。

注意:第一个命令(label onto)标记了提交变基到的修订版本;名称 onto 只是一个约定,旨在向 --onto 选项致敬。

还可以通过添加 merge <merge-head> 形式的命令来从头开始引入全新的合并提交。此形式将生成一个初步的提交消息,并始终打开编辑器让用户编辑。例如,当一个主题分支结果证明解决了多个问题,并希望被拆分为两个甚至更多主题分支时,这很有用。考虑这个待办事项列表:

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

配置

本节中以下所有内容均从 git-config[1] 文档中选择性地包含。内容与彼处相同:

rebase.backend

用于变基的默认后端。可选值为 applymerge。将来如果 merge 后端获得了 apply 后端的所有剩余功能,此设置可能会被弃用。

rebase.stat

是否显示自上次变基以来上游变化的差异统计(diffstat)。默认为 false。

rebase.autoSquash

如果设置为 true,则交互模式下默认启用 git-rebase[1]--autosquash 选项。这可以使用 --no-autosquash 选项覆盖。

rebase.autoStash

设置为 true 时,在操作开始前自动创建一个临时暂存条目,并在操作结束后应用它。这意味着你可以在脏工作树上运行变基。但是,请谨慎使用:成功变基后的最终暂存应用可能会导致复杂的冲突。此选项可以被 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] 中所指定,用于交互式变基期间的待办事项列表。该格式将自动在格式前加上提交哈希。

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] 套件的一部分