git pull --rebase 是否与 git pull 当本地主分支上没有完成任何工作时相同?

Is git pull --rebase the same as git pull when no work is done on the local master branch?

我知道 git rebasegit merge 之间的区别是 git rebase 将一个分支的开头移动到另一个分支的顶端,而 git merge 创建代表(即包括或集成)来自另一个分支的分支提交的新提交。例如:

因为 git pull 等价于 git fetch + git merge,而 git pull --rebase 等价于 git fetch + git rebase,假设你有一个本地主分支和一个远程主分支:

- o - o - o - H - A - B - C (master)
               \
                P - Q - R (origin/master)

HEAD 在 C

如果你 git pull 你最终会得到这个:

- o - o - o - H - A - B - C - X (master)
               \             /
                P - Q - R --- (origin/master)

HEAD 现在位于 X,它是远程存储库提交历史的提交 R,因为合并提交 (X) 表示(即包含或集成)已合并的远程分支的提交历史

另一方面,如果您这样做 git pull --rebase,您将得到这样的结果:

- o - o - o - H - P - Q - R - A' - B' - C' (master)
                          |
                          (origin/master)

如您所见,git pullgit pull --rebase 不同,因为 git pull --rebase 在 C' 处给出了 HEAD,这是本地主分支的提交 C,而 git pull HEAD was at R. 换句话说,通过 git pull 的提交历史的内容与来自 git pull --rebase 的提交历史的内容相同,但是提交中的提交顺序历史不同

但是当你在本地 master 分支上没有完成任何工作时从远程分支拉取时会发生什么:

本地master分支和远程master分支:

- o - o - o - H (master)
               \
                P - Q - R (origin/master)

HEAD 在 H

如果你 git pull 你最终会得到这个:

- o - o - o - H - X (master)
               \             \
                P - Q - R --- (origin/master)

HEAD 现在将位于 X,这是 repo 的提交 R,因为合并提交代表已合并的远程分支

另一方面,如果你这样做 git pull --rebase,你会这样结束吗?:

- o - o - o - H - P - Q - R (master)
                          |
                          (origin/master)

意味着 HEAD 在 R 这意味着 git pull --rebasegit pull 相同,当没有在本地分支上完成任何工作时

git pull 运行s git merge时,它既不指定--ff-only也不指定--no-ff,除非你特别要求它。

[given the commit graph]

...--o--o--o--H   <-- master (HEAD)
               \
                P--Q--R   <-- origin/master

[you suggest that git merge, and therefore git pull, would produce]

...--o--o--o--H---------X   <-- master (HEAD)
               \       /
                P--Q--R   <-- origin/master

事实并非如此——无论如何,默认情况下并非如此。如果你 运行 git merge --no-ff origin/master,你 得到这个结果。如果您 运行 git merge --ff-only,或允许默认操作,您将得到:

...--o--o--o--H
               \
                P--Q--R   <-- master (HEAD), origin/master

Git 称其为 fast-forward。当它专门 git merge 执行此操作时,1 Git 将其称为 fast-forward 合并 ,尽管没有实际合并发生:Git 实际上只是在移动分支名称 master 时执行提交 Rgit checkout,以便它指向提交 R(并离开HEAD 附加到分支名称 master).

--no-ff选项告诉Git:即使可以也不要做fast-forward。结果是一个新的合并提交(或者错误,或者其他任何可能出错的地方,当然)。 --ff-only选项告诉Git:如果你能做到fast-forward,就去做;如果不是,什么也不做,只是出错。

当您有 git pull 运行 git merge 时,您将获得相同的选项集。如果 fast-forward (a) 可能且 (b) 允许,则 git merge 的结果与 git rebase 的结果相同:名称 master 得到更新并且提交图中没有任何变化。如果 fast-forward 不可能 ,或者被 --no-ff 禁止,git merge 的结果不同于 git rebase因为有一个新的合并提交 X.

新合并提交 X 内容 — 保存的快照与您的提交 R 相匹配。但由于 RX 是不同的提交,它们具有不同的哈希 ID,因此后续的 Git 操作将表现不同。


1git pushgit fetch 都可以对通过 refspecs 更新的引用执行此操作。当 git push 以 fast-forward 方式移动服务器的分支时,您通常看不出有什么特别之处。当 git push 不能 以这种方式移动分支时,您通常会收到服务器的拒绝。 git fetch 注释,而不是 ! rejected (non-fast-forward),是 (forced update)。 (好吧,这是三个 (!) git fetch 注释之一。)请参阅下面的示例。

这里要强调的是,fast-forwarding是一个属性的label motion。如果在动议之前标识的提交(在本例中为提交 H)是之后要标识的提交的祖先,即提交 R.[=73=,那么 fast-forward 是可能的]

这是一个非fast-forward 获取更新的示例。我有 Git 的 Git 存储库的本地克隆。上游的一个分支名为 pu,代表 提议更新。这个分支会定期倒带并用新的提交重建:有人提出了一些新功能,编写了第一个尝试,然后进行测试。发现了错误,重新设计了功能,丢弃了进入的提交,以支持进入 pu 分支的新改进的提交。所以我的 origin/pu 将是 force-updated:

$ git fetch
remote: Counting objects: 509, done.
remote: Compressing objects: 100% (214/214), done.
remote: Total 509 (delta 325), reused 400 (delta 295)
Receiving objects: 100% (509/509), 468.38 KiB | 1.52 MiB/s, done.
Resolving deltas: 100% (325/325), done.
From [url]
   d8fdbe21b5..95628af9bb  next       -> origin/next
 + b6268ac8c6...465df7fb28 pu         -> origin/pu  (forced update)
   8051bb0ca1..f0bab95d47  todo       -> origin/todo
$ 

请注意,我的 Git 更新了我的 origin/next、我的 origin/pu 和我的 origin/todo。其中两个更新是 fast-forward 操作:例如,提交 d8fdbe21b5(我的旧 origin/next)是 95628af9bb(我更新的 origin/next)的祖先。但是 b6268ac8c6,我的老 origin/pu 不是 465df7fb28 的祖先。我的 Git 更新了我的 origin/pu 无论如何 ,无法访问提交 b6268ac8c6(除了通过 reflogs)。

为了宣布这一事实,git fetch:

  1. 在行前加上 +;
  2. 在更新中打印了三个点,而不是两个;和
  3. 在行尾添加 (forced update)

因为对 origin/nextorigin/todo 的更改是 fast-forward 操作,所以我的 Git 没有对它们做任何额外说明。