根据我对git pull --rebase origin master
的理解,它应该等同于运行以下命令:
(from branch master): $ git fetch origin
(from branch master): $ git rebase origin/master
我似乎找到了一些不按预期工作的情况。在我的工作区中,我有以下设置:
origin/master
引用远程master
origin
master
设置为跟踪origin/master
,并且通过多次提交隐藏在 master之后。feature
设置为通过多次提交跟踪master
的本地分支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
答案 0 :(得分:8)
(编辑,2016年11月30日:另见this answer至Why is git rebase discarding my commits?。现在几乎可以肯定它是由于fork-point选项。)
手动和基于pull
的{{1}}之间存在一些差异(现在在2.7中比在git rebase
选项之前的git版本中更少在--fork-point
)。而且,我怀疑你的自动保留合并可能会涉及到。这有点难以确定,但是你的本地分支跟随你的另一个本地分支这一事实是非常具有启发性。同时,旧的git merge-base
脚本最近也在C中重写,因此很难看到它的作用(尽管你可以将环境变量git pull
设置为GIT_TRACE
来制作git show你在内部运行它们的命令。)
在任何情况下,这里都有两到三个关键项目(取决于你如何计算和拆分它们,我将它变为3):
1
运行git pull
,然后根据说明运行git fetch
或git merge
,但当它运行git rebase
时,它会使用新的叉点从上游的转折中恢复的机器"。
当git rebase
运行时没有参数时,它有一个特殊情况,它调用fork-point机制。使用参数运行时,除非使用git rebase
明确请求,否则将禁用fork-point机制。
当指示--fork-point
保留合并时,它使用交互式rebase代码(非交互式)。我不确定这在这里真的很重要(因此"可能涉及"上面)。通常它会使合并变平,只有交互式rebase脚本才能保存它们(这段代码实际上会重新进行合并,因为没有其他方法可以处理它们。)
这里最重要的项目(肯定)是叉点代码。此代码使用reflog来处理通过绘制提交图的一部分而最佳显示的案例。
在正常情况下(需要没有叉点的东西)rebase case你有这样的东西:
git rebase
其中... - A - B - C - D - E <-- origin/foo
\
I - J - K <-- foo
和A
是您启动分支时的提交(因此B
是合并基础),B
到C
是您通过E
从远程选择的新提交,git fetch
到I
是您自己的提交。 rebase代码将K
复制到I
,将第一个副本附加到K
,将第二个副本附加到E
,将第三个副本附加到副本 - 的 - I
Git计算出 - 或者习惯 - 承诺使用J
进行复制,即使用当前分支的名称(git rev-list origin/foo..foo
)来查找{ {1}}并向后工作,以及上游(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
使用K
,我们选择要复制的提交 B' - C - D - E <-- origin/foo
/
... - A - B <-- [origin/foo@{n}]
\
I - J - K <-- foo
,git rev-list origin/foo..foo
,B
和I
,然后尝试粘贴它们像往常一样在J
之后;但我们并非希望复制K
,因为它实际上来自E
,并已被其自己的副本B
取代。
fork point代码的作用是查看origin
的reflog,看看B'
是否可以在某个时间到达。也就是说,它不仅检查origin
(查找B
并扫描回origin/master
然后E
),还检查B'
(直接指向{{} 1}},可能取决于您运行A
),origin/master@{1}
的频率,等等。在B
上可以从任何 git fetch
访问的任何提交都包括在内,以便在图中找到最低公共祖先节点时考虑(即,它们全部被视为选项成为origin/master@{2}
打印出来的合并基础。
(值得注意的是这里的缺陷:这种自动化的fork point检测只能找到在维护reflog条目时可以访问的提交,在这种情况下默认为30天。但是,这与您的问题不太相关。)
在您的情况下,您有三个分支名称(因此涉及三个reflog):
foo
,由origin/master@{n}
更新(git merge-base
分行origin/master
时的第一步)git fetch
,由您(通过正常提交)和git pull
(master
的第二步)和master
,由您(通过正常提交)和git rebase
(第二个 git pull
的第二步更新:您&#34 ;从你自己获取&#34;一个无操作,然后在feature
上重新git rebase
。两个rebase都使用git pull
(因此是非交互式交互模式)和feature
运行,其中 master
提交ID通过运行{{}找到1}}。第一个rebase的 --preserve-merges
为--onto new-tip fork-point
(嗯,fork-point
), git merge-base --fork-point upstream-name HEAD
为第二个rebase是upstream-name
(origin/master
)。
这个应该所有Just Work。如果您在整个过程开始时的提交图表与您所描述的类似:
refs/remotes/origin/master
然后第一个upstream-name
引入了一些提交,并使master
指向新提示:
refs/heads/master
然后第一个rebase找不到要复制的内容(... - A - B <-- master, origin/master
\
I - J - K <-- feature
和fetch
的合并基础 - origin/master
= fork-point(master,origin / master)-is just { {1}}所以没有什么可复制的),给出:
C - D - E <-- origin/master
/
... - A - B <-- master, origin/master@{1}
\
I - J - K <-- feature
第二次获取来自你自己和完全没有操作/跳过,留下这作为第二个rebase的输入。 master
目标为B
,提交B
,而B
( C - D - E <-- master, origin/master
/
... - A - B <-- master@{1}, origin/master@{1}
\
I - J - K <-- feature
)和--onto
的分叉也提交{{} 1}},让提交master
到E
照常复制HEAD
。
如果某些提交被删除,在此过程中出现问题,但我无法看到。