理解"git pull --rebase"vs"git rebase"

ash*_*ays 6 git version-control rebase

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

(from branch master):  $ git fetch origin
(from branch master):  $ git rebase origin/master
Run Code Online (Sandbox Code Playgroud)

我似乎找到了一些不按预期工作的情况.在我的工作区中,我有以下设置:

  • 远程分支origin/master引用分支masterorigin
  • 分支master设置为跟踪origin/master,并由几个提交落后于主.
  • 分支feature设置为跟踪当地的分支机构master,以及领先的master通过多次提交.

有时,我会通过运行以下一系列步骤来丢失提交

(from branch master):  $ git pull --rebase
(from branch master):  $ git checkout feature
(from branch feature): $ git pull --rebase
Run Code Online (Sandbox Code Playgroud)

在这一点上,我所feature面临的一些提交现在已经丢失了.现在,如果我重置我的位置,而是执行以下操作:

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

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

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

注意:如果重要的话,我的git pull --rebase这里应该被理解为--rebase=preserve.我有以下内容~/.gitconfig:

[pull]
    rebase = preserve
Run Code Online (Sandbox Code Playgroud)

tor*_*rek 8

(编辑,2016年11月30日:又见这个答案到为什么git的重订丢弃我提交?现在这几乎是一定的,这是由于叉点选项.)

目前有手动和之间有一些差别pull为基础的git rebase(现在少在2.7比有在比罗马的Git版本--fork-point中选择git merge-base).而且,我怀疑你的自动保留合并可能会涉及到.这有点难以确定,但是你的本地分支跟随你的另一个本地分支这个事实正在变得非常具有启发性.同时,老git pull剧本改写也用C最近所以这是很难看到它做什么(尽管你可以设置环境变量GIT_TRACE,以1使git的节目你的命令,因为它运行在内部他们).

在任何情况下,这里有两三个关键项目(取决于你如何计算和拆分它们,我会把它变成3):

  • git pullgit fetch然后运行,git merge或者git rebase按照指令运行,但是当它运行时,git rebase它使用新的fork-point机器"从上游rebase恢复".

  • 当git rebase没有参数运行时,它有一个特殊的情况,它调用fork-point机器.使用参数运行时,除非明确请求,否则将禁用fork-point机制--fork-point.

  • 当git rebase指示保留合并时,它使用交互式rebase代码(非交互式).我不确定这在这里真的很重要(因此上面可能会涉及).通常它会使合并变平,只有交互式rebase脚本才能保存它们(这段代码实际上重新进行了合并,因为没有其他方法可以处理它们).

这里最重要的项目(肯定)是叉点代码.此代码使用reflog来处理通过绘制提交图的一部分而最佳显示的案例.

在正常情况下(没有叉点需要)rebase案例你有这样的事情:

... - A - B - C - D - E   <-- origin/foo
            \
              I - J - K   <-- foo
Run Code Online (Sandbox Code Playgroud)

其中,A和B是承诺你有,当你开始你的分支(因此B是合并基础),C通过E你是通过远程拿起新的提交git fetch,并I通过K在自己的提交.底垫代码副本I通过K,将所述第一副本E,第二到拷贝-OF- I,第三到拷贝-OF- 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]
Run Code Online (Sandbox Code Playgroud)

这种方法的问题发生在上游 - origin/foo这里 - 自身重新定位时.例如,假设origin某人被强行推动,以便B被一个B'具有不同提交措辞的新副本所取代(也可能是一个不同的树,但我们希望,这些都不会影响我们 - I通过 - K).起点现在看起来像这样:

          B' - C - D - E    <-- origin/foo
        /
... - A - B   <-- [origin/foo@{n}]
            \
              I - J - K   <-- foo
Run Code Online (Sandbox Code Playgroud)

使用git rev-list origin/foo..foo,我们选择提交B,I和J,以及K复制,并尝试E像往常一样粘贴它们; 但我们不想复制,B因为它真的来自origin并已被自己的副本取代B'.

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

(值得注意的是这里存在的缺陷:这种自动化叉点检测只能找到维护reflog条目时可以访问的提交,在这种情况下默认为30天.但是,这与您的问题不是特别相关.)


在您的情况下,您有三个分支名称(因此涉及三个reflog):

  • origin/master,由git fetch(git pull分支的第一步master)更新
  • master,由你(通过正常提交)和git rebase(你的第二步git pull)更新,和
  • feature,由你(通过正常提交)和git rebase(你的第二步 git pull:你从自己"获取",无操作,然后重新feature开始master)更新.

两个底垫与运行--preserve-merges(因此无相互作用交互模式)和,其中,所述提交ID是通过运行发现.该用于第一变基是(当然,)和用于第二变基是().--onto new-tip fork-pointfork-pointgit merge-base --fork-point upstream-name HEADupstream-nameorigin/masterrefs/remotes/origin/masterupstream-namemasterrefs/heads/master

这应该都是Just Work.如果整个过程开始时的提交图与您所描述的类似:

... - A - B   <-- master, origin/master
            \
              I - J - K   <-- feature
Run Code Online (Sandbox Code Playgroud)

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

              C - D - E   <-- origin/master
            /
... - A - B   <-- master, origin/master@{1}
            \
              I - J - K   <-- feature
Run Code Online (Sandbox Code Playgroud)

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

              C - D - E   <-- master, origin/master
            /
... - A - B   <-- master@{1}, origin/master@{1}
            \
              I - J - K   <-- feature
Run Code Online (Sandbox Code Playgroud)

第二次获取是来自你自己和完全没有操作/跳过,留下这作为第二个rebase的输入.的--onto目标是master其为提交E和的叉点HEAD(feature)和master也被提交B,留下提交I通过K后复制E如常.

如果某些提交被删除,在这个过程中出现问题,但我看不清楚是什么.

  • 哇.这是一个很棒的答案,并提供了很多洞察这里发生的事情.我将花一些时间试图重新创造导致这个问题的环境,并看看这个答案是否能够揭示正在发生的事情.我怀疑这会直接引导我,但我很想找到确切的原因.如果我找到任何东西,我会通知你. (2认同)