首页 > 解决方案 > 当本地主分支上没有工作时, git pull --rebase 是否与 git pull 相同?

问题描述

git rebase我知道和之间的区别在于git mergegit rebase一个分支的开头移动到另一个分支的尖端,在那里git merge创建一个新的提交,代表(即包含或集成)来自另一个分支的分支提交。例如:

由于git pull等价于git fetch+ git merge,并且git pull --rebase等价于git fetch+ git rebase,假设您有一个本地 master 分支和一个远程 master 分支:

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

头在 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 pull与在 C' 处给出 HEAD 不同,后者是本地 master 分支的提交 C,而HEAD 的使用时间是在 R 处。换句话说,提交历史记录的内容与的提交历史的内容,但提交历史中的提交顺序不同git pull --rebasegit pull --rebasegit pullgit pullgit pull --rebase

但是,当您在本地主分支上未完成任何工作时从远程分支拉取时会发生什么:

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

- 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 --rebase相同git pull

标签: gitmergegit-mergegit-rebasegit-pull

解决方案


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

[给定提交图]

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

[你建议 git merge,因此 git pull,会产生]

...--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 称之为快进。当它特别git merge这样做时,1 Git 将其称为fast-forward merge,尽管没有发生实际的合并:Git 实际上只是在移动分支名称时执行 a git checkoutof commit以使其指向提交(并保留附加到分支名称) .RmasterRHEADmaster

--no-ff选项告诉 Git:即使可以,也不要进行快进。 结果是一个新的合并提交(或者一个错误,或者其他任何可能出错的地方,当然)。该--ff-only选项告诉 Git:如果你可以快进,那就去做;如果没有,不要做任何事情,只是出错。

git pull运行后,您git merge将获得相同的选项集。如果快进是 (a) 可能的并且 (b) 允许的,则结果与 a 的结果git merge相同git rebase:名称master将被更新,并且提交图中的任何内容都不会发生任何变化。如果快进是不可能的,或者被禁止--no-ff了,则 a 的结果与 a 的结果git merge不同,git rebase因为有一个新的合并提交X

新合并提交的内容(保存的快照)与提交的内容相X匹配R。但是由于RX是不同的提交,它们具有不同的哈希 ID,后续 Git 操作的行为也会有所不同。


1两者都git push可以git fetch对通过 refspecs 更新的引用执行此操作。当git push以快进方式移动服务器的分支时,您通常看不到任何特别之处。当git push 无法以这种方式移动分支时,您通常会收到服务器的拒绝。注释,而git fetch不是! rejected (non-fast-forward),是(forced update)。(嗯,这是三个 (!)git fetch注释之一。)请参见下面的示例。

我想在这里强调的是,快进是标签运动的一个属性。H如果在动议之前确定的提交(在这种情况下是提交)是要在之后确定的提交的祖先,即 commit ,则快进是可能的R

这是一个非快进获取更新的示例。我有一个用于 Git 的 Git 存储库的本地克隆。上游分支之一被命名pu,代表建议更新。这个分支会定期重绕并用新的提交重建:有人提出了一些新特性,写了第一个尝试,然后进行测试。错误被发现,功能被重新设计,进入的提交被丢弃,有利于进入pu分支的新改进。所以我的origin/pu将被强制更新:

$ 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 更新了 my origin/next、 myorigin/pu和 my origin/todo. 其中两个更新是快进操作:例如,提交d8fdbe21b5(my old origin/next) 是95628af9bb(my updated origin/next) 的祖先。只是b6268ac8c6,我老了origin/pu,已经不是祖宗了465df7fb28。我的 Git 更新了我origin/pu 的无论如何,失去了提交的访问权限b6268ac8c6(通过 reflogs 除外)。

为了宣布这一事实,git fetch

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

因为这些更改origin/next快进操作,所以我的 Git 对它们只字未提。origin/todo


推荐阅读