且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

git pull --rebase 在同事的 git push --force 后丢失提交

更新时间:2021-08-29 05:00:55

TL;DR:这是叉点代码

您正在获得 git rebase --fork-point 的效果,它也故意从 您的 存储库中删除 Dan 的提交.另请参阅Git rebase - 在分叉点模式下提交选择(尽管在我的回答中我没有提到我会在这里).

TL;DR: it's the fork point code

You are getting the effect of git rebase --fork-point, which deliberately drops Dan's commit from your repository too. See also Git rebase - commit select in fork-point mode (although in my answer there I don't mention something I will here).

如果您自己运行 git rebase,您选择是否使用 --fork-point.--fork-point 选项在以下情况下使用:

If you run the git rebase yourself, you choose whether --fork-point is used. The --fork-point option is used when:

  • 你在没有 参数的情况下运行 git rebase(暗示了 --fork-point),或者
  • 你运行 git rebase --fork-point [] .
  • you run git rebase with no <upstream> argument (the --fork-point is implied), or
  • you run git rebase --fork-point [<arguments>] <upstream>.

这意味着要在没有应用 --fork-point 的情况下基于上游 ,您应该使用:

This means that to rebase on your upstream without having --fork-point applied, you should use:

git rebase @{u}

或:

git rebase --no-fork-point

有些细节与 Git 版本有关,因为 --fork-point 仅在 Git 2.0 版中成为一个选项(但从那时起就被 git pull 秘密完成)1.6.4.1,随着方法变得越来越复杂,直到发明了整个 --fork-point 东西).

Some details are Git-version-dependent, as --fork-point became an option only in Git version 2.0 (but was secretly done by git pull ever since 1.6.4.1, with methods growing more complex until the whole --fork-point thing was invented).

如您所知,git push --force 粗暴地覆盖了分支指针,删除了一些现有的提交.但是,您期望您的 git pull --rebase恢复删除的提交,因为您自己已经有了它.为了命名方便,让我们使用你的命名,当 Brian 强制推送时,Dan 的提交被丢弃.(作为助记符,让我们说Dan got Dropped".)

As you already know, git push --force rudely overwrites the branch pointer, dropping some existing commit(s). You expected, though, that your git pull --rebase would restore the dropped commit, since you already had it yourself. For naming convenience, let's use your naming, where Dan's commit gets dropped when Brian force-pushes. (As a mnemonic, let's say "Dan got Dropped".)

有时会!有时,只要您的 Git 在您的 存储库中有 Dan 的提交,并且您在您的 历史记录中有 Dan 的提交,当您重新设置提交时,Dan 的提交就会恢复.这包括您丹的情况.然而,有时它不会,这包括您是 Dan 的情况.换句话说,它根本不是基于你是谁.

Sometimes it will! Sometimes, as long as your Git has Dan's commit in your repository, and you have Dan's commit in your history, Dan's commit will get restored when you rebase your commits. This includes the case where you are Dan. And yet, sometimes it won't, and this also include the case where you are Dan. In other words, it's not based on who you are at all.

完整的答案有点复杂,值得注意的是,这种行为是您可以控制的.

The complete answer is a bit complicated, and it's worth noting that this behavior is something you can control.

首先,让我们做一个简短的说明:git pull 本质上只是 git fetch 后跟 git merge>git rebase.1 您可以通过提供 --rebase 或设置配置条目 branch.分支名称.rebase.但是,你可以自己运行git fetch,然后自己运行git mergegit rebase,这样你就获得了访问其他选项.2

First, let's make a brief note: git pull is, in essence, just git fetch followed by either git merge or git rebase.1 You choose in advance which command to run, by supplying --rebase or setting a configuration entry, branch.branch-name.rebase. However, you can run git fetch yourself, and then run git merge or git rebase yourself, and if you do it this way, you gain access to additional options.2

其中最重要的是能够在选择主要选项之前检查获取的结果(合并与变基).换句话说,这让您有机会看到有一个提交被删除.如果您之前执行了 git fetch 并获得了 Dan 的提交,那么 - 无论是否有任何干预工作,您可能会或可能不会合并 Dan 的提交 - 完成第二次 git fetch,你会看到这样的:

The most important of these is the ability to inspect the result of the fetch before choosing your primary option (merge vs rebase). In other words, this gives you a chance to see that there was a commit dropped. If you had done a git fetch earlier and gotten Dan's commit, then—with or without any intervening work where you may or may not have incorporated Dan's commit—done a second git fetch, you would see something like this:

 + 5122532...6f1308f pu         -> origin/pu  (forced update)

注意(强制更新)"注释:这就是告诉您 Dan 掉线的原因.(这里使用的分支名称是 pu,它是 Git 的 Git 存储库中的一个,它定期进行强制更新;我只是剪切并粘贴了一个实际的 git fetch输出在这里.)

Note the "(forced update)" annotation: this is what tells you that Dan got Dropped. (The branch name used here is pu, which is one in the Git repo for Git that regularly gets force-updated; I just cut-and-pasted an actual git fetch output here.)

1有几个细微的技术差异,尤其是在非常旧的 Git 版本(1.8.4 之前)中.还有,正如我最近被提醒的,另一个特殊情况,对于在当前分支上没有提交的存储库中的 git pull(通常,到一个新的空存储库):这里 git pull 既不调用 git merge 也不 git rebase,而是运行 git read-tree -m,如果成功,则设置分支名称本身.

1There are several niggling technical differences, especially in very old versions of Git (before 1.8.4). There is also, as I was recently reminded, one other special case, for a git pull in a repository that has no commits on the current branch (typically, into a new empty repository): here git pull invokes neither git merge nor git rebase, but rather runs git read-tree -m and, if that succeeds, sets the branch name itself.

2我认为您可以在命令行上提供所有必要的参数,但这不是我的意思.特别是,能够在获取和第二步之间运行other Git 命令是我们想要的.

2I think you can supply all the necessary arguments on the command line, but that's not what I mean. In particular, the ability to run other Git commands between the fetch and the second step is what we want.

要了解git rebase 的主要和最基本的事情是它复制 提交.为什么本身是 Git 的基础:没有任何东西——没有人,也不是 Git 本身——可以改变任何东西在提交(或任何其他 Git 对象)中,就像真名"Git 对象的内容是其内容的加密散列.3因此,如果您从数据库中取出提交,请修改任何内容——即使是一个位——然后去把对象返回,你会得到一个新的、不同的哈希:一个新的和不同的提交.它可能与原始提交极其相似,但如果其中有任何一点在任何方面有所不同,那么它就是一个新的、不同的提交.

The main and most fundamental thing to know about git rebase is that it copies commits. The why is itself fundamental to Git: nothing—no one, and not Git itself—can change anything in a commit (or any other Git object), as the "true name" of a Git object is a cryptographic hash of its contents.3 Hence if you take a commit out of the database, modify anything—even a single bit—and go to put the object back in, you get a new, different hash: a new and different commit. It can be extremely similar to the original, but if any bit of it is different in any way, it's a new, different commit.

要了解这些副本的工作原理,请至少绘制提交的一部分.该图只是一系列提交,从最新的(或tip)提交开始,其真实名称哈希 ID 存储在分支名称中.我们说名称​​指向提交:

To see how these copies work, draw at least part of the commit graph. The graph is just a series of commits, starting from the newest—or tip—commit, whose true-name hash ID is stored in the branch's name. We say that the name points to the commit:

            D   <-- master

提交,我在此处称为 D,包含(作为其散列提交数据的一部分)其 提交的散列 ID,即提交在我们制作 D 之前,分支的尖端.所以它指向"它的父级,它的父级指向更远的地方:

The commit, which I've called D here, contains (as part of its hashed commit data) the hash ID of its parent commit, i.e., the commit that was the tip of the branch before we made D. So it "points to" its parent, and its parent points further back:

... <- C <- D   <-- master

内部箭头像这样全部向后这一事实通常不是很重要,所以我在这里倾向于省略它们.当一个字母的名字不是很重要时,我只是为每个提交画一个圆点:

The fact that the internal arrows are all backwards like this is usually not very important, so I tend to omit them here. When the one-letter names are not very important I just draw a round dot for each commit:

...--o--o   <-- branch

对于 branch 来分支"master,我们应该绘制两个分支:

For branch to "branch off from" master, we should draw both branches:

A--B--C--D     <-- master
    
     E--F--G   <-- branch

注意提交 E 指向提交 B.

Note that commit E points back to commit B.

现在,如果我们想要re-base branch,那么它在提交D之后出现(现在是master),我们需要copy提交 E 到一个新的提交 E' 是一样好";C,除了它有 D 作为它的父级(当然还有一个不同的快照作为它的源基础):

Now, if we want to re-base branch, so that it comes after commit D (which is now the tip of master), we need to copy commit E to a new commit E' that is "just as good as" C, except that it has D as its parent (and of course has a different snapshot as its source base as well):

           E'  <-- (temporary)
          /
A--B--C--D     <-- master
    
     E--F--G   <-- branch

我们现在必须用 FG 重复这个,当我们都完成后,让名称 branch 指向最后一个副本, G', 放弃原来的链条,换新的:

We must now repeat this with F and G, and when we are all done, make the name branch point to the last copy, G', abandoning the original chain in favor of the new one:

           E'-F'-G'  <-- branch
          /
A--B--C--D           <-- master
    
     E--F--G         [abandoned]

这就是 git rebase 的全部内容:我们挑选出一些要复制的提交;我们将它们复制到某个新位置,一次一个,以父级优先的顺序(相对于更典型的子级优先向后 Git 顺序);然后我们将分支 label 重新指向最后复制的提交.

This is what git rebase is all about: we pick out some set of commits to copy; we copy them to some new position, one at a time, in parent-first order (vs the more typical child-first backwards Git order); and then we re-point the branch label to the last-copied commit.

请注意,这甚至适用于空情况.如果名称 branch 直接指向 B 并且我们基于 master 将其变基,我们会复制 B 之后的所有零提交code>,将它们复制到 D 之后.然后将标签branch重新指向上次复制的提交,即none,这意味着我们重新指向branch提交D.在 Git 中,有几个分支名称都指向同一个提交是完全正常的.Git 通过读取包含分支名称.git/HEAD 知道你在哪个分支.分支本身——提交图的某个部分——由决定.这意味着分支"这个词.不明确:参见我们所说的分支"究竟是什么意思?

Note that this works even for the null case. If the name branch points directly to B and we rebase it on master, we copy all zero commits that come after B, copying them to come after D. Then re-point the label branch to the last-copied commit, which is none, which means we re-point branch to commit D. It's perfectly normal, in Git, to have several branch names all pointing to the same commit. Git knows which branch you are on by reading .git/HEAD, which contains the name of the branch. The branch itself—some portion of the commit graph—is determined by the graph. This means the word "branch" is ambiguous: see What exactly do we mean by "branch"?

还要注意提交 A 根本没有父级.这是存储库中的第一次 提交:之前没有提交.Commit A 因此是一个 root 提交,这只是一种表达没有父级的提交"的奇特方式.我们也可以与两个或更多的父母一起提交;这些是合并提交.(不过,我没有在这里画任何东西.对包含合并的分支链进行 rebase 通常是不明智的,因为实际上不可能对合并进行 rebase,并且 git rebase 必须重新执行 合并来近似它.通常 git rebase 只是完全省略合并,这会导致其他问题.)

Note also that commit A has no parents at all. It's the first commit in the repository: there was no previous commit. Commit A is therefore a root commit, which is just a fancy way to say "a commit with no parents". We can also have commits with two or more parents; these are merge commits. (I did not draw any here, though. It's often unwise to rebase branch chains that contain merges, since it's literally impossible to rebase a merge and git rebase has to re-perform the merge to approximate it. Normally git rebase just omits merges entirely, which causes other problems.)

3显然,根据Pigeonhole Principle,任何哈希将较长的位串减少为固定长度的 k 位密钥必须在某些输入上发生冲突.Git 哈希函数的一个关键要求是避免意外冲突.密码"部分对 Git 来说并不是很重要,它只是让某人变得困难(但当然并非不可能)故意造成碰撞.冲突导致 Git 无法添加新对象,因此它们很糟糕,但是——除了实现中的错误——它们实际上并没有破坏 Git 本身,只是进一步使用 Git 来处理你自己的数据.

3Obviously, by the Pigeonhole Principle, any hash that reduces a longer bit-string to a fixed-length k-bit key must necessarily have collisions on some inputs. A key requirement for a Git hash function is that it avoid accidental collisions. The "cryptographic" part is not really crucial to Git, it just makes it hard (but of course not impossible) for someone to deliberately cause a collision. Collisions cause Git to be unable to add new objects, so they are bad, but—aside from bugs in the implementation—they don't actually break Git itself, just the further usage of Git for your own data.

变基的一个问题在于识别哪些提交要复制.

One problem with rebasing lies in identifying which commits to copy.

大多数时候,这似乎很简单:您希望 Git 复制的提交,而不是其他人的.但这并不总是正确的——在有管理员和经理等的大型分布式环境中,有时某人重新设定其他人的提交是合适的.无论如何,这不是 Git 最初的做法.相反,Git 使用图形.

Most of the time, it seems easy enough: you want Git to copy your commits, and not someone else's. But that's not always true—in large, distributed environments, with administrators and managers and so on, sometimes it's appropriate for someone to rebase someone else's commits. In any case, this is not how Git does it in the first place. Instead, Git uses the graph.

命名提交——例如,编写branch——往往不仅会选择那个提交,还会选择那个提交的父提交、父提交等等,所有回到根提交的方式.(如果有一个合并提交,我们通常选择 all 它的父提交,并同时跟随所有它们回到根.一个图可以有多个根,所以这让我们选择多个返回多个根的链,以及返回单个根的分支和合并链.)当从一个提交开始并执行这些父遍历时,我们将找到的所有提交的集合称为 一组可达的提交.

Naming a commit—e.g., writing branch—tends to select not just that commit, but also that commit's parent commit, the parent's parent, and so on, all the way back to the root commit. (If there is a merge commit, we usually select all of its parent commits, and follow all of them back towards the root simultaneously. A graph can have more than one root, so this lets us select multiple strands going back to multiple roots, as well as branch-and-merge strands going back to a single root.) We call the set of all commits that we find, when starting from one commit and doing these parent traversals, the set of reachable commits.

出于许多目的,包括git rebase,我们需要使这个整体选择停止,我们使用 Git 的奇特集合操作来做到这一点.如果我们将 master..branch 写成一个修订选择器,这意味着:所有提交都可以从 branch 的尖端到达,除了任何可以从 主人."再看这张图:

For many purposes, including git rebase, we need to make this en-masse selection stop, and we use Git's fancy set operations to do that. If we write master..branch as a revision selector, this means: "All commits reachable from the tip of branch, except for any commits reachable from the tip of master." Look at this graph again:

A--B--C--D     <-- master
    
     E--F--G   <-- branch

branch 可达的提交是GFEB> 和 A.master 可访问的提交是 DCBA.所以master..branch的意思是:从较大的A+B+E+F+G集合中减去A+B+C+D集合.

The commits reachable from branch are G, F, E, B, and A. The commits reachable from master are D, C, B, and A. So master..branch means: subtract away the A+B+C+D set from the bigger A+B+E+F+G set.

在集合减法中,删除一开始不存在的东西是微不足道的:你什么都不做.所以我们从第二组中删除 A+B,留下 E+F+G.或者,我喜欢使用我无法在 *** 上绘制的方法:将提交着色为红色(停止)和绿色(去),跟随图表中的所有向后箭头,从红色开始表示被禁止的提交(master) 和绿色用于提交 (branch).只要确保红色覆盖绿色,或者你先做红色,不要在做绿色时重新着色.很直观4先做绿色,再用红色覆盖;或者先做红色并且覆盖,给出相同的结果.

In set subtraction, removing something that was never there in the first place is trivial: you just do nothing. So we remove A+B from the second set, leaving E+F+G. Alternatively, I like to use a method that I can't draw on ***: color the commits red (stop) and green (go), following all the backwards arrows in your graph, starting with red for commits that are forbidden (master) and green for commits to take (branch). Just make sure that red overwrites green, or that you do red first and don't re-color them when you're doing green. It's intuitively obvious4 that doing green first, then overwriting with red; or doing red first and not overwriting, gives the same result.

无论如何,这是 git rebase 选择要复制的提交的一种方式.变基文档将其称为 .你写:

Anyway, this is one way git rebase selects commits to copy. The rebase documentation calls this the <upstream>. You write:

git checkout branch; git rebase master

并且Git知道将master提交为红​​色,当前分支branch提交为绿色,然后只复制绿色的.此外,同一个名字——master——告诉 git rebase 把副本放在哪里.这是非常优雅和高效的:一个参数,master,告诉 Git复制什么副本放在哪里.

and Git knows to color master commits red and current-branch branch commits green, and then copy just the green ones. Moreover, that same name—master—tells git rebase where to put the copies. This is quite elegant and efficient: a single argument, master, tells Git both what to copy and where to put the copies.

问题是,它并不总是有效.

The problem is, it doesn't always work.

有几种常见的情况会导致它崩溃.当您想进一步限制副本时会发生一种情况,例如,将一个大分支分解为两个较小的分支.另一种情况是,您自己的一些提交(但不是全部)已经被复制(樱桃挑选或压缩-合并")到您要重新定位到的另一个分支中.罕见但并非闻所未闻,有时上游故意丢弃一些提交,你也应该这样做.

There are several common cases where it breaks down. One occurs when you want to limit the copies even more, e.g., to break up one big branch into two smaller ones. Another occurs when some, but not all, of your own commits have already been copied (cherry-picked or squash-"merged") into another branch you're rebasing onto. Rarer, but not unheard-of, sometimes an upstream has deliberately discarded some commits, and you should too.

对于其中一些情况,git rebase 可以使用 git patch-id:它实际上可以判断一个提交被复制了,只要两个提交具有相同的补丁 ID.对于其他人,您必须使用 --onto 标志手动拆分 rebase target(rebase 调用此 <newbase>):

For some of these cases, git rebase can deal with them using git patch-id: it can actually tell that a commit got copied, as long as the two commits have the same patch ID. For others, you must manually split the rebase target (rebase calls this <newbase>) using the --onto flag:

git rebase --onto <newbase> <upstream>

将要复制的提交限制为 ..HEAD 中的提交,同时在 之后开始复制.这 - 将副本的目标与 参数分开 - 意味着您现在可以***选择删除 的任何 正确提交,而不是由副本去哪里"决定的一些集合.

which limits the commits to copy to those in <upstream>..HEAD, while starting the copies after <target>. This—splitting the targeting of the copies away from the <upstream> argument—means that you are now free to choose any <upstream> that removes the right commits, rather than some set determined by "where the copies go".

4这是数学家不想写出证明时使用的短语.:-)

4This is the phrase mathematicians use when they don't want to write out a proof. :-)

如果您定期将提交重新定位到 origin/whatever 分支(或类似),即其本身,定期重新定位,其目标是删除 提交,可能很难决定要复制哪些提交.但是,如果您在 origin/whatever reflog 中有一系列提交哈希,这些哈希表明某些提交 之前存在但不再存在,Git 可以使用它来丢弃,从 to-copy 集中,部分或全部这些提交.

If you regularly rebase commits onto an origin/whatever branch (or similar) that is, itself, also regularly rebased specifically with a goal of removing commits, it can be hard to decide which commits to copy. But if you have, in your origin/whatever reflog, a series of commit hashes that show that some commits were there before but are no longer, it's possible for Git to use this to discard, from the to-copy set, some or all of those commits.

我不完全确定 --fork-point 是如何在内部实现的(它没有很好的文档记录).对于这个答案,我创建了一个测试存储库.不出所料,该选项依赖于顺序:git merge-base --fork-point origin/master topic 返回与 git merge-base --fork-point topic origin/不同的结果大师.

I was not entirely sure how --fork-point is implemented internally (it's not very well documented). For this answer, I made a test repository. The option was, unsurprisingly, order-dependent: git merge-base --fork-point origin/master topic returns a different result from git merge-base --fork-point topic origin/master.

对于这个答案,我查看了源代码.这表明 Git 在 first 非选项参数的引用日志中查找——调用这个 arg1——然后​​使用它使用 next 这样的参数 arg2 解析为提交 ID,完全忽略任何其他参数.基于此,git merge-base --fork-point $arg1 $arg2的结果本质上是5的输出:

For this answer, I looked at the source code. This shows that Git looks in the reflog of the first non-option argument—call this arg1—and then uses it to find a merge base using the next such argument arg2 resolved to a commit ID, completely ignoring any additional arguments. Based on this, the result of git merge-base --fork-point $arg1 $arg2 is essentially5 the output of:

git merge-base $arg2 $(git log -g --format=%H $arg1)

正如文档所说:

更一般地,在计算合并基础的两个提交中,一个由命令行上的第一个提交参数指定;另一个提交是(可能是假设的)提交,它是命令行上所有剩余提交的合并.

More generally, among the two commits to compute the merge base from, one is specified by the first commit argument on the command line; the other commit is a (possibly hypothetical) commit that is a merge across all the remaining commits on the command line.

因此,如果指定了两个以上的提交,则 merge base 不一定包含在每个提交参数中.这与 git-show-branch(1)--merge-base 选项一起使用时.

As a consequence, the merge base is not necessarily contained in each of the commit arguments if more than two commits are specified. This is different from git-show-branch(1) when used with the --merge-base option.

所以 --fork-point 尝试找到第二个参数的当前哈希值与上游 的当前值的假设合并基数之间的合并基数> 其所有 reflog 记录的值.这就是导致排除已删除提交的原因,例如此处示例中 Dan 的提交.

So --fork-point tries to find the merge base between the current hash for the second argument, and the hypothetical merge base of the current value of the upstream and all of its reflog-recorded values. This is what leads to the exclusion of a dropped commit, such as the one by Dan in our example here.

记住,使用 --fork-point 模式只是在内部修改 参数给 git rebase(没有也改变它的 --onto 目标).例如,假设有一次,上游有:

Remember, using --fork-point mode merely modifies, internally, the <upstream> argument to git rebase (without also changing its --onto target). Let's say, for instance, that at one time, the upstream had:

...--o--B1--B2--B3--C--D    <-- upstream
                 
                  E--F--G   <-- branch

可以说,--fork-point 的目标是检测这种形式的重写:

The goal, as it were, of --fork-point is to detect a rewrite of this form:

...--o-------------C--D     <-- upstream
      
       B1--B2--B3           <-- upstream@{1}
                
                 E--F--G    <-- branch

和淘汰"通过选择B3作为内部,提交B1B2B3代码> 参数.如果您省略--fork-point 选项,Git 会以这种方式查看所有内容:

and "knock out" commits B1, B2, and B3 by selecting B3 as the internal <upstream> argument. If you leave out the --fork-point option, Git views everything this way instead:

...--o-------------C--D     <-- upstream
      
       B1--B2--B3--E--F--G  <-- branch

这样所有 B 提交都是我们的".上游分支上的提交是 DCC 的父 o(及其父,到根).

so that all the B commits are "ours". The commits on the upstream branch are D, C, and C's parent o (and its parents, on to the root).

在我们的特殊情况下,Dan 的提交——被删除的提交——类似于这些 B 提交之一.它与 --fork-point 一起删除,并与 --no-fork-point 一起保留.

In our particular case, Dan's commit—the one that gets dropped—resembles one of these B commits. It's dropped with --fork-point and kept with --no-fork-point.

5如果使用--all,这将产生多个合并基,命令失败,不打印任何内容.如果生成的(单一)合并基础不是引用日志中已经存在的提交,则该命令也会失败.第一种情况发生在作为最近祖先的纵横合并.第二种情况发生在所选祖先的年龄足以从 reflog 中过期,或者一开始就没有在其中(我很确定这两种情况都是可能的).我这里有第二种失败的例子:

5If, were one to use --all, this would produce multiple merge bases, the command fails, and prints nothing. If the resulting (singular) merge base is not a commit that is already in the reflog, the command also fails. The first case occurs with criss-cross merges as the nearest ancestor. The second case occurs when the selected ancestor is old enough to have expired from the reflog, or was never in it in the first place (I am pretty sure both are possible). I have an example of this second kind of failure right here:

$ arg1=origin/next
$ arg2=stash-exp
$ git merge-base --all $arg2 $(git log -g --format=%H $arg1)
3313b78c145ba9212272b5318c111cde12bfef4a
$ git merge-base --fork-point $arg1 $arg2
$ echo $?
1

认为在这里跳过 3313b78... 的想法是它是那些可能的假设提交"之一.我从文档中引用,但实际上它是与 git rebase 一起使用的正确承诺,而且它是 is 使用的 --fork-point.

I think the idea of skipping 3313b78... here is that it is one of those "possibly hypothetical commits" I quote from the documentation, but in fact it would be the right commit to use with git rebase, and it's the one that is used without --fork-point.

在 2.0 之前的 Git 版本(或者可能是 1.9 后期)中,正在变基的 git pull 会计算这个分叉点,但 git rebase 从来没有这样做.这意味着如果你想要这种行为,你必须使用git pull来获得它.现在 git rebase 有了 --fork-point,你可以选择什么时候获取它:

In Git versions before 2.0 (or maybe a late 1.9), git pull that was rebasing would compute this fork point, but git rebase never did. This meant that if you wanted this kind of behavior, you had to use git pull to get it. Now that git rebase has --fork-point, you can choose when to get it:

  • 如果您确实确实想要它,请添加该选项.
  • 使用 --no-fork-point 或明确的上游(@{u} 如果你有一个默认的上游就足够了)如果你肯定不'想要它.
  • Add the option if you definitely do want it.
  • Use --no-fork-point or an explicit upstream (@{u} suffices if you have a default upstream) if you definitely don't want it.

如果你运行git pull,你没有--no-fork-point选项.6是否git pull origin master(假设当前分支的上游是 origin/master)会像 git rebase origin/master 那样抑制分叉点,我不知道:我主要避免 git pull. 根据测试(见 打破本杰明下面的评论),在 Git 2.0+ 中,git pull --rebase 总是使用 fork-point 模式,即使有额外的参数.

If you run git pull, you do not have the --no-fork-point option.6Whether git pull origin master (assuming the current branch's upstream is origin/master) suppresses the fork-point the way git rebase origin/master would, I do not know: I mostly avoid git pull. According to testing (see Breaking Benjamin's comment below), in Git 2.0+, git pull --rebase always uses fork-point mode, even with additional arguments.

6至少,到目前为止,我刚刚测试它以确保.

6At least, as of now when I just tested it to make sure.