为什么git pull -r没有将本地提交进行变基操作?

6

我遇到了一个场景,其中我不理解结果。

我的团队正在使用提交记录进行一个功能分支的开发:

A--B--C

一位同事提交了两个新的代码提交:

A--B--C--D--E

我没有意识到有其他人正在将代码推送到这个分支,因此我强制推送了一个修改过的提交:

A--B--C'

在意识到我的错误后,我建议他尝试(在他的仓库中,D和E仍然存在):
git pull -r
git push

我的想法是他的两次提交(D、E)已不再属于该分支的历史记录,所以在拉取时,Git 将试图将它们合并到历史记录中。我预期 D 和 E 将被重新基于 C',但这并没有发生。相反地(引用解释):
$ git pull -r
+ commit...commit branch -> origin/branch (forced update)
$ git push
Everything up-to-date

而且D和E都不见了。我想也许他在他的版本库上做了一些其他的操作,所以它可能处于某种未知状态,因此我们在reflog中找到了E的哈希值,并多次执行git reset --hard <E>尝试再次操作(主要是因为我很好奇为什么它没有按照我的预期进行),但得到了相同的结果。

我确定我误解了一些事情,但我不知道是什么。为什么git pull -r没有将“新”的提交D和E(即我无意中从历史记录中删除的提交)在C'之上变基?


正如一些人指出的那样:我的期望应该包括C。我没有考虑到这一点,但我理解为什么,这是有道理的。

我的期望是,“新”的提交(现在更新为C、D、E;即使它能够工作,也不是我想要的)会被变基,但实际上并没有,我不明白为什么。

@matt提到过这些提交之前已经被推送过,这很有趣。如果有人可以详细解释一下这个机制,那似乎是一个潜在的答案。


4
我强制推送了一个修订的提交。这就是为什么有“带租约的强制推送”,以便您不能犯那种错误。 - matt
了解“force with lease” - Allen
3
看起来是一个重复的问题:同事 git push --force 后,git pull --rebase 丢失了提交记录 - TTT
3个回答

4
这种情况发生的原因是因为git pull -r(或git pull --rebase)与以下命令不完全相同:
git fetch
git rebase @{u}

如果您运行了那些命令,您将会在A-B-C'的基础上rebase C-D-E,结果是:A-B-C'-C-D-E。(根据您的修订,C可能已经被删除了。)因为pull -r并不完全等同于这些命令,所以在强制推送后,结果就像您所见到的那样。这是强制推送不受欢迎的另一个原因。还有我不喜欢pull的另一个原因。我总是更喜欢分别输入这两个命令,以防万一。作为一个旁注(在评论中提到),如果您使用git push --force-with-lease而不是git push --force,您会注意到远程分支的更改,并能够对您的分支进行rebase,而不是强制推送。我强烈建议始终使用--force-with-lease(或者也许是更新的--force-if-includes),除非您有特定的人为情况需要使用--force

2
我已确认过了。这很奇怪,文档说 git pull -r 会获取并变基。那么 git pull -r 到底在做什么? - Schwern
2
@Schwern 我需要确认一下我有时间深入研究它。但我认为它使用了不同的分叉点设置。 - TTT
1
有一些花招,git使用reflog来确定分支“真正”分叉的位置。我认为这可能与此有关。(如果是这样,当您知道正在尝试从错误的强制推送中恢复时,可以抑制该行为,这就是在此处发生的事情,虽然有点不得体)但是我没有意识到这对于pull -rrebase是不同的;我以后会看看这个。 - Mark Adelsberger
2
哦,如果在git rebase中明确提供了上游,则--fork-point的默认假设与否则不同。因此,这可能不是“git pull -r不等同于两个命令”的问题,而是“git pull -r使用隐式上游,使命令行为不同。” - Mark Adelsberger
2
@MarkAdelsberger 哈哈哈,我一定是有先见之明。我试图开个玩笑,结果看看我发现了什么。我猜这是一个重复的问题。 - TTT
显示剩余5条评论

2
"git pull -r" 使用 "git rebase --fork-point"。它选择 E 作为分叉点。
引用:
当 --fork-point 激活时,fork_point 将被用来计算要 rebase 的提交集,其中 fork_point 是 git merge-base --fork-point 命令的结果(参见 git-merge-base(1))。如果 fork_point 最终为空,则会使用 作为后备方案。
详细解释请参见 Torek 的回答
我们可以复制这种情况。
远程仓库不一定要通过网络连接。我已经通过使用 git init --bare 命令创建一个远程仓库和两个克隆来测试了这一点。以下是一个设置脚本。
#!/bin/sh

# Remote repo, two clones.
git init --bare upstream.git
git clone upstream.git me.git
git clone upstream.git coworker.git

# Push A-B-C
cd me.git
git commit --allow-empty -m A
git commit --allow-empty -m B
git commit --allow-empty -m C
git push

cd ../coworker.git
git pull

# Co-worker adds D-E and pushes
git commit --allow-empty -m D
git commit --allow-empty -m E
git push

# You amend C and force push
cd ../me.git
git commit --amend --allow-empty -m C1
git push --force

cd ..

此时,如果我们执行-r命令,他们的工作将会被删除,正如你所观察到的那样。
cd coworker.git
git pull -r

如果我们改为使用等效的命令,即 git fetchgit rebase origin/main,则可以按预期工作。
cd coworker.git
git fetch
git rebase origin/main

为什么?运行`git pull --rebase=interactive`时发现它选错了范围。
$ git pull --rebase=interactive
noop

# Rebase 01431d7..01431d7 onto d914686 (1 command)

它使用了由git merge-base --fork-point origin/main选择的“fork-point”作为E,而不是C。torek的答案可以解释原因
而且,正如其他人所说,这可以通过 --force-with-lease 避免。

可能不是一个 bug。请看我对 TTT 回答的评论。 - Mark Adelsberger

-1

在你强制推送之后,你的远程仓库可能会变成这样:

A--B--C'(remote/master)
    \
     --C--D--E

尝试执行 git fetch 然后执行 git log --oneline --graph --all,看看是否显示。

我对变基不是很了解,但这里有官方的 Git 教程:https://git-scm.com/book/en/v2/Git-Branching-Rebasing


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接