了解 "git pull --rebase" 与 "git rebase"

Understanding "git pull --rebase" vs "git rebase"

根据我对git pull --rebase origin master的理解,应该相当于运行以下命令:

(from branch master):  $ git fetch origin
(from branch master):  $ git rebase origin/master

我似乎发现了一些无法按预期工作的情况。在我的工作区中,我有以下设置:

有时,我会通过 运行 以下步骤序列

丢失提交
(from branch master):  $ git pull --rebase
(from branch master):  $ git checkout feature
(from branch feature): $ git pull --rebase

此时,我在 feature 上的少数提交现在已经丢失。现在,如果我重置我的位置,而是执行以下操作:

(from branch feature): $ git reset --hard HEAD@{2} # rewind to before second git pull
(from branch feature): $ git rebase master

提交已正确应用,我在 feature 上的新提交仍然存在。这似乎与我对 git pull 工作原理的理解直接矛盾,除非 git fetch . 做了一些比我预期的更奇怪的事情。

不幸的是,这并不是所有提交都 100% 可重现的。但是,当它确实适用于提交时,它每次都有效。

注意: 我这里的 git pull --rebase 实际上应该读作 --rebase=preserve,如果这很重要的话。我的 ~/.gitconfig 中有以下内容:

[pull]
    rebase = preserve

(编辑,2016 年 11 月 30 日:另见 this answer to Why is git rebase discarding my commits?。现在几乎可以肯定这是由于分叉点选项造成的。)

手动操作和基于 pullgit rebase 之间存在 一些差异(现在 2.7 中的差异少于 git 早于 git merge-base 中的 --fork-point 选项)。而且,我怀疑可能涉及您的自动保留合并。这有点难以确定,但您的本地分支机构跟随您的其他正在重新定位的本地分支机构这一事实非常具有启发性。同时,旧的 git pull 脚本最近也用 C 重写了,所以很难看出它做了什么(尽管你可以将环境变量 GIT_TRACE 设置为 1 以使 git 显示你命令它 运行 内部)。

无论如何,这里有两个或三个关键项目(取决于您如何计算和拆分它们,我将其分为 3 个):

  • git pull 运行s git fetch,然后根据指令 git mergegit rebase,但当 运行s git rebase 它使用新的叉点机械 "recover from an upstream rebase".

  • git rebase 是没有参数的 运行 时,它有一个调用叉点机制的特殊情况。当 运行 带有参数时,叉点机制被禁用,除非使用 --fork-point.

  • 明确请求
  • 当指示 git rebase 保留合并时,它使用交互式 rebase 代码(非交互式)。我不确定这在这里真的很重要(因此上面 "may be involved")。通常它会消除合并,只有交互式 rebase 脚本有代码来保留它们(这段代码实际上是重新进行合并,因为没有其他方法可以处理它们)。

这里(当然)最重要的项目是分叉点代码。此代码使用 reflog 来处理通过绘制部分提交图最好显示的情况。

在正常情况下(不需要 fork 点的东西)rebase 情况下你有这样的事情:

... - A - B - C - D - E   <-- origin/foo
            \
              I - J - K   <-- foo

其中 AB 是您开始分支时的提交(因此 B 是合并基础),CE 是您通过 git fetch 从远程获取的新提交,而 IK 是您自己的提交。 rebase 代码将 I 复制到 K,将第一个副本附加到 E,第二个附加到 the-copy-of-I,第三个附加到 the-copy-的-J.

Git 弄清楚——或者习惯了,无论如何—— 承诺使用 git rev-list origin/foo..foo 进行复制,即使用当前分支的名称 ( foo) 查找 K 并向后工作,其上游名称 (origin/foo) 查找 E 并向后工作。向后行进在合并基地停止,在本例中为 B,复制的结果如下所示:

... - A - B - C - D - E   <-- origin/foo
           \            \
            \             I' - J' - K'   <-- foo
             \
              I - J - K   [foo@{1}: reflog for foo]

当上游(此处为 origin/foo)自身重新设置基址时,此方法会出现问题。比方说,在 origin 上有人强制推送 B 被一个新的副本 B' 替换,具有不同的提交措辞(也可能是不同的树,但是,我们希望,没有什么会影响我们的 I-through-K)。起点现在看起来像这样:

          B' - C - D - E    <-- origin/foo
        /
... - A - B   <-- [origin/foo@{n}]
            \
              I - J - K   <-- foo

使用 git rev-list origin/foo..foo,我们会 select 提交 BIJK 进行复制,并且尝试像往常一样将它们粘贴在 E 之后;但是我们不想复制B,因为它确实来自origin并且已经被它自己的副本B'.[=115所取代=]

分叉点代码所做的是查看 origin 的 reflog 以查看 B 是否在某个时间可达。也就是说,它不仅检查 origin/master(找到 E 并扫描回 B',然后 A),而且检查 origin/master@{1}(直接指向 B,可能,取决于你 运行 git fetch),origin/master@{2},等等的频率。 foo 上可从 any origin/master@{n} 访问的任何提交都包括在内,以供在图中查找最低公共祖先节点时考虑(即,它们都被处理作为选项成为 git merge-base 打印出来的合并基础。

(值得注意的是这里的一个缺陷:这种自动分叉点检测只能找到在维护 reflog 条目的时间内可达的提交,在本例中默认为 30 天。然而,这不是与您的问题特别相关。)


在你的例子中,你有 三个 个分支名称(因此三个 reflogs):

  • origin/master,由 git fetch 更新(你的 git pull while 分支 master 的第一步)
  • master,由您(通过正常提交)和 git rebase(您的 git pull 的第二步)和
  • 更新
  • feature,由您(通过正常提交)和 git rebase(您的 second git pull 的第二步更新: 你 "fetch" 来自你自己,一个空操作,然后在 master 上变基 feature)。

两个变基都是 运行 和 --preserve-merges(因此是非交互交互模式)和 --到 <em>new-tip</em> <em>fork-point</em>,其中 fork-point 提交 ID 由 运行ning [=179= 找到]git merge-base --fork-point upstream-name HEAD。第一个变基的upstream-nameorigin/master(好吧,refs/remotes/origin/master)和upstream-name 第二个 rebase 是 master (refs/heads/master).

应该一切正常。如果您在整个过程开始时的提交图与您所描述的类似:

... - A - B   <-- master, origin/master
            \
              I - J - K   <-- feature

然后第一个 fetch 引入一些提交并使 origin/master 指向新提示:

              C - D - E   <-- origin/master
            /
... - A - B   <-- master, origin/master@{1}
            \
              I - J - K   <-- feature

然后第一个 rebase 找不到任何可复制的东西(masterB 的合并基础—B=fork-point(master, origin/master) —只是 B 所以没有什么可复制的),给出:

              C - D - E   <-- master, origin/master
            /
... - A - B   <-- master@{1}, origin/master@{1}
            \
              I - J - K   <-- feature

第二次获取来自您自己,并且完全 no-op/skipped,将其作为第二次变基的输入。 --onto 目标是 master 这是提交 E 并且 HEAD (feature) 和 master 的分叉点也是提交 B,留下 IK 照常在 E 之后复制。

如果某些提交被删除,则表示此过程中出现了问题,但我看不出是什么。