【问题标题】:Why didn't git pull -r rebase the local commits? [duplicate]为什么不 git pull -r rebase 本地提交? [复制]
【发布时间】:2021-06-06 16:29:53
【问题描述】:

我遇到了一个我不明白结果的场景。

我的团队正在开发一个带有提交的功能分支:

A--B--C

一位同事推送了两个新的提交:

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

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

A--B--C'

在我意识到我的错误后,我建议他尝试(在他的 repo 上,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 也不见了。我想也许他在他的 repo 上做了一些其他的操作,所以它可能处于某种未知状态,所以我们在 reflog 中找到了 E 的哈希,并做了几次git reset --hard <E> 再试一次(主要是因为我很好奇为什么它没有像我预期的那样发生)并且得到了相同的结果。

我确定我误解了什么,但我不确定是什么。为什么git pull -r 没有在 C' 之上重新定义“新”提交 D 和 E(即我无意中从历史中删除的那些)?


正如一些人指出的那样:C 也应该包含在我的期望中。我没有考虑过这个,但我明白为什么,这是有道理的。

我的期望是“新”提交(现在更新为 C、D、E;即使它起作用了,也不会是我想要的)会被重新设置,但它们不是,我不'不明白为什么。

@matt 关于之前推送的提交的评论很有趣。如果有人可以详细说明该机制,这似乎是一个潜在的答案。

【问题讨论】:

标签: git


【解决方案1】:

发生这种情况的原因是git pull -r(或git pull --rebase)与以下命令不同:

git fetch
git rebase @{u}

如果您改为运行这些命令,您将在 A-B-C' 之上重新定位 C-D-E,从而产生:

A-B-C'-C-D-E。 (也许 C 会根据你的修改而失败。)

因为pull -r不完全是幕后的那些命令,强推后的结果会如你所见。

这是不赞成强制推动的另一个原因。这也是我不喜欢拉的另一个原因。以防万一,我总是喜欢分别输入这两个命令。

作为旁注(并在评论中提到),如果您使用 git push --force-with-lease 而不是 git push --force,您会注意到远程分支上的更改,并且能够重新设置您的分支而不是强制推送。我强烈建议始终使用--force-with-lease(或者可能是较新的--force-if-includes),除非您有特定的人为场景需要--force

【讨论】:

  • 我已经确认了这一点。太奇怪了,文档说git pull -r 会获取和变基。 git pull -r 在做什么?
  • @Schwern 我必须确认何时有时间深入研究。但我认为它使用了不同的分叉点设置。
  • 有一些恶作剧,git 使用 reflog 来确定分支“relaly”分歧的位置。我认为这将与此有关。 (如果是这样,当你知道你正试图从糟糕的力量推动中恢复时,这种行为可以被抑制,这 - 冒着不雅的风险 - 就是这里发生的事情。)但我没有意识到这是不同的pull -rrebase;以后我得看看这个
  • 哦,如果上游明确提供给git rebase,则--fork-point 的默认假设是不同的,如果不是。所以这可能不是“git pull -r 不等同于这两个命令”,而是“git pull -r 使用隐式上游,这使得命令的行为不同。”
  • @MarkAdelsberger 哈哈。我一定有预感。我去那里开个玩笑和look what I found。我想这是一个重复。
【解决方案2】:

在你强制推送之后,你的遥控器大概是这样的:

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

【讨论】:

    【解决方案3】:

    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's answer for a detailed explanation


    我们可以重现这种情况。

    远程存储库不必通过网络。我已经通过设置一个带有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 选择为 E 而不是 C 的“叉点”。torek's answer can explain why


    正如其他人所说,--force-with-lease 可以避免这种情况。

    【讨论】:

    • 可能不是错误。在 TTT 的回答中查看我的 cmets。
    猜你喜欢
    • 2017-06-30
    • 2020-06-08
    • 2016-10-27
    • 2011-03-09
    • 1970-01-01
    • 2015-03-21
    • 1970-01-01
    • 1970-01-01
    • 2011-09-11
    相关资源
    最近更新 更多