了解“git pull --rebase” vs“git rebase” [英] Understanding "git pull --rebase" vs "git rebase"

查看:136
本文介绍了了解“git pull --rebase” vs“git rebase”的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

 (来自分支主人):$ git fetch origin 
(来自分支主人):$ git rebase origin / master

我似乎发现了一些情况,这并不像预期的那样正常工作。在我的工作区中,我有以下设置:




  • 分支 origin / master 参考分支 master 在远程 origin

  • 分支 master 被设置为跟踪 origin / master ,并在 master 后面进行多次提交。

  • 分支功能设置为跟踪本地分支 master 提前 master 多次提交。



有时候,运行以下顺序的步骤:

$ $ p $ (来自分支主机):$ git pull --rebase
(来自分支主机):$ git checkout feature
(来自分支功能):$ git pull --rebase

现在,我在功能上的少量提交已经丢失。现在,如果我重置我的位置,而是做以下操作:

 (来自分支功能):$ git reset --hard HEAD @ {2}#回退到第二个git pull 
(来自分支功能)之前:$ git rebase master

提交已正确应用,并且我对功能的新提交仍然存在。这似乎与我对 git pull 工作方式的理解直接抵触,除非 git fetch。做的事情超出了我的预料。

不幸的是,对于所有提交,这不是100%可重复的。注意: 我的 git pull - 当它为提交工作时,它每次都有效。如果这很重要的话,这里的rebase 实际上应该被读为 - rebase = preserve 。我在〜/ .gitconfig 中有以下内容:

  [pull ] 
rebase =保存


解决方案

2016年11月30日:另请参阅此答案为什么git rebase放弃我的提交?。现在几乎可以肯定这是由于fork-point选项造成的。)



手动和拉 - 基于 git rebase 之间的一些区别git的版本早于 git merge-base 中的 - fork-point 选项。而且,我怀疑你的自动保存合并可能会涉及到。这有点难以确定,但是您的本地分支跟随您的其他当地分行正在进行重新组织的事实颇具启发性。与此同时,旧的 git pull 脚本最近也被重写了,所以很难看出它的作用(尽管你可以设置环境变量 GIT_TRACE 1 来让git显示你的命令,因为它在内部运行它们)。



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


  • git pull 运行 git fetch ,然后 git merge git rebase 每条指令,但是当它运行 git rebase 时,它会使用新的fork-point机器

  • git rebase 没有参数运行时,它具有从上游rebase恢复。

  • 一个调用叉点机械的特例。使用参数运行时,除非通过 - fork-point 明确请求,否则fork-point机器将被禁用。

    当指示 git rebase 保留合并时,它会使用交互式底码(非交互式)。我不确定这是否真的很重要(因此可能参与上面)。通常它会消除合并,只有交互式rebase脚本有代码来保存它们(这段代码实际上是重新合并的,因为没有其他方法可以处理它们)。


    ul>

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



    在一个正常的(没有叉点的东西需要)rebase的情况下,你有这样的事情:

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

    其中 A B 是您在开始分支时提交的提交(以便 B 是合并基础), C E 是您通过<$ c $从远程获取的新提交c> git fetch I K 是您自己的提交。 rebase代码复制 I K ,将第一个副本附加到 E ,第二个拷贝到 I ,第三个拷贝到 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 - 通过 - ķ)。现在的出发点如下所示:

      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 ,因为它确实来自 origin 并且已被替换为它自己的副本 B'



    fork point代码的作用是查看reflog origin 来查看是否可以在某个时间 B 可达。也就是说,它不仅检查 origin / master (找到 E 并扫描回到 B ',然后 A ),但也是 origin / master @ {1} 直接到 B ,可能取决于你运行 git fetch )的次数, origin /主@ {2} ,依此类推。从任何 origin / master @ {n} 可到达的任何对 foo 的提交都是包括在图表中寻找最低公共祖先节点的考虑因素(也就是说,它们都被视为选项,成为合并基础,打印出 git merge-base >)。

    (这里值得注意的是这种排序的缺陷:这种自动化的叉点检测只能找到在reflog条目被维护时可达到的提交,案例默认为30天,但这与您的问题并无特别关系。)






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


    $ lt; code> origin / master
    code>,它由 git fetch 更新(在分支<$时, git pull >的第一步) c code $ master $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $'和 git rebase (你的 git pull >的第二步)和

  • 特性 ,它由你(通过正常的提交)和 git rebase (你的第二步 second git pull :你从自己抓取,无操作,然后在 master 上重新命名特性 >



  • 两个rebase都以 - preserve-merges 运行(因此非交互式交互模式)和 - 到新尖端 fork-point ,其中 fork-point 通过运行 git merge-base --fork-point upstream-name HEAD 。第一个rebase的 upstream-name origin / master (好,<$第二个rebase的c $ c> refs / remotes / origin / master )和 upstream-name code> master refs / heads / master )。

    应该全部正常工作。如果您在整个过程开始时的提交图如您所描述的那样:

      ...  -  A  - B < - 主,起源/主
    \
    I - J - K < - 特征

    ,那么第一个 fetch 会带来一些提交并使 origin / master 指向新建议:

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

    然后第一个rebase找不到任何内容( master B - B = fork-point(master,origin / master) - 只是 B ,所以没有什么可以复制),给出:

    $ pre code> C - D - E < - master,起源/大师
    /
    ... - A - B < - master @ {1},orig in / master @ {1}
    \
    I - J - K < - feature

    第二次抓取是从您自己开始的,并且完全跳过了,完成后将其作为第二次rebase的输入。 - 到目标是 master ,它是commit E HEAD (特性)和 master 也是提交 B ,留下提交 I K 像往常一样在 E 之后复制。



    如果某些提交被删除,这个过程,但我看不到。


    According to my understanding of git pull --rebase origin master, it should be the equivalent of running the following commands:

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

    I seem to have found some case where this doesn't work as expected. In my workspace, I have the following setup:

    • branch origin/master references branch master on remote origin
    • branch master is set up to track origin/master, and is behind master by several commits.
    • branch feature is set up to track local branch master, and ahead of master by several commits.

    Sometimes, I will lose commits by running the following sequence of steps

    (from branch master):  $ git pull --rebase
    (from branch master):  $ git checkout feature
    (from branch feature): $ git pull --rebase
    

    At this point, the few commits ahead I was on feature have now been lost. Now, if I reset my position, and instead do the following:

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

    The commits have been applied correctly and my new commits on feature are still present. This seems to directly contradict my understanding of how git pull works, unless git fetch . does something stranger than I expected.

    Unfortunately, this is not 100% reproducible for all commits. When it does work for a commit, though, it works every time.

    Note: My git pull --rebase here should actually be read as a --rebase=preserve, if that matters. I have the following in my ~/.gitconfig:

    [pull]
        rebase = preserve
    

    解决方案

    (Edit, 30 Nov 2016: see also this answer to Why is git rebase discarding my commits?. It is now virtually certain that it is due to the fork-point option.)

    There are a few differences between manual and pull-based git rebase (fewer now in 2.7 than there were in versions of git predating the --fork-point option in git merge-base). And, I suspect your automatic preserve-merges may be involved. It's a bit hard to be sure but the fact that your local branch follows your other local branch which is getting rebased is quite suggestive. Meanwhile, the old git pull script was also rewritten in C recently so it's harder to see what it does (though you can set environment variable GIT_TRACE to 1 to make git show you commands as it runs them internally).

    In any case, there are two or three key items here (depending on how you count and split these up, I'll make it into 3):

    • git pull runs git fetch, then either git merge or git rebase per instructions, but when it runs git rebase it uses the new fork-point machinery to "recover from an upstream rebase".

    • When git rebase is run with no arguments it has a special case that invokes the fork-point machinery. When run with arguments, the fork-point machinery is disabled unless explicitly requested with --fork-point.

    • When git rebase is instructed to preserve merges, it uses the interactive rebase code (non-interactively). I'm not sure this actually matters here (hence "may be involved" above). Normally it flattens away merges and only the interactive rebase script has code to preserve them (this code actually re-does the merges since there's no other way to deal with them).

    The most important item here (for sure) is the fork point code. This code uses the reflog to handle cases best shown by drawing part of the commit graph.

    In a normal (no fork point stuff needed) rebase case you have something like this:

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

    where A and B are commits you had when you started your branch (so that B is the merge-base), C through E are new commits you picked up from the remote via git fetch, and I through K are your own commits. The rebase code copies I through K, attaching the first copy to E, the second to the-copy-of-I, and the third to the-copy-of-J.

    Git figures out—or used to, anyway—which commits to copy using git rev-list origin/foo..foo, i.e., using the name of your current branch (foo) to find K and work backwards, and the name of its upstream (origin/foo) to find E and work backwards. The backwards march stops at the merge base, in this case B, and the copied result looks like this:

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

    The problem with this method occurs when the upstream—origin/foo here—is itself rebased. Let's say, for instance, that on origin someone force-pushed so that B was replaced by a new copy B' with different commit wording (and maybe a different tree as well, but, we hope, nothing that affects our I-through-K). The starting point now looks like this:

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

    Using git rev-list origin/foo..foo, we'd select commits B, I, J, and K to be copied, and try to paste them on after E as usual; but we don't want to copy B as it really came from origin and has been replaced with its own copy B'.

    What the fork point code does is look at the reflog for origin to see if B was reachable at some time. That is, it checks not just origin/master (finding E and scanning back to B' and then A), but also origin/master@{1} (pointing directly to B, probably, depending on how frequently you run git fetch), origin/master@{2}, and so on. Any commits on foo that are reachable from any origin/master@{n} are included for consideration in finding a Lowest Common Ancestor node in the graph (i.e., they're all treated as options to become the merge base that git merge-base prints out).

    (It's worth noting a defect of sorts here: this automated fork point detection can only find commits that were reachable for the time that the reflog entry is maintained, which in this case defaults to 30 days. However, that's not particularly relevant to your issue.)


    In your case, you have three branch names (and hence three reflogs) involved:

    • origin/master, which is updated by git fetch (the first step of your git pull while branch master)
    • master, which is updated by both you (via normal commits) and git rebase (the second step of your git pull), and
    • feature, which is updated by both you (via normal commits) and git rebase (the second step of your second git pull: you "fetch" from yourself, a no-op, then rebase feature on master).

    Both rebases are run with --preserve-merges (hence non-interacting interactive mode) and --onto new-tip fork-point, where the fork-point commit ID is found by running git merge-base --fork-point upstream-name HEAD. The upstream-name for the first rebase is origin/master (well, refs/remotes/origin/master) and the upstream-name for the second rebase is master (refs/heads/master).

    This should all Just Work. If your commit graph at the start of the whole process is something like what you've described:

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

    then the first fetch brings in some commits and makes origin/master point to the new tip:

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

    and the first rebase then finds nothing to copy (the merge-base of master and BB=fork-point(master, origin/master)—is just B so there is nothing to copy), giving:

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

    The second fetch is from yourself and a no-op/skipped entirely, leaving this as the input to the second rebase. The --onto target is master which is commit E and the fork-point of HEAD (feature) and master is also commit B, leaving commits I through K to copy after E as usual.

    If some commit(s) are being dropped, something is going wrong in this process, but I can't see what.

    这篇关于了解“git pull --rebase” vs“git rebase”的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆