当本地主分支没有进行任何工作时,git pull --rebase和git pull命令是否相同?

3

我知道 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将会指向远程仓库的提交历史中的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',即本地主分支上的提交C,而使用git pull时HEAD位于R。换句话说,通过git pull获取的提交历史与通过git 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现在将位于仓库的提交R处的X位置。

另一方面,如果您执行了git pull --rebase,您会得到什么结果?

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

意思是HEAD位于R,这意味着当本地分支上没有进行任何工作时,git pull --rebasegit pull是相同的。

在您的最后一个示例中,这两个最终结果并不完全相同...(合并提交 X) 代码库在两种情况下都是相同的,但历史遍历不同,因此日志会有所不同。 - Romain Valeri
你在上一个 git pull 的例子中的假设是错误的,如果你从远程拉取并且本地分支没有新的提交,那么默认情况下就不会有新的合并提交,但是会有快进。你可以通过添加 --no-ff 强制执行合并提交来改变这种行为。回答你的问题,如果没有本地提交,那么 pull 和 pull --rebase 是相同的。 - William Kinaan
1个回答

3
当运行git pull时,它会执行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将此称为“快进”。当特别使用`git merge`时,Git将其称为“快进合并”,尽管实际上没有进行任何合并:Git确实只是对提交`R`执行了`git checkout`,同时移动分支名称`master`,使其指向提交`R`(并将`HEAD`附加到分支名称`master`)。
`--no-ff`选项告诉Git:“即使可以,也不要快进。”结果是一个新的合并提交(或错误,或其他可能出现的问题)。`--ff-only`选项告诉Git:“如果可以快进,请快进;如果不行,请不要做任何事情,只需报错。”
当您运行`git pull`时,会运行`git merge`,您将获得相同的选项集。如果可以并且允许快进,则`git merge`的结果与`git rebase`的结果相同:名称`master`被更新,并且提交图表中的任何内容都不会发生任何更改。如果快进不可行,或者通过`--no-ff`被禁止,则`git merge`的结果与`git rebase`的结果不同,在这种情况下会产生一个新的合并提交`X`。
您新合并提交`X`的“内容”——保存的快照——与您的提交`R`相匹配。但由于`R`和`X`是不同的提交,它们具有不同的哈希ID,随后的Git操作将以不同的方式运行。
强调这里的快进是标签运动的属性。如果在运动之前标识的提交(在本例中为提交`H`)是要在之后标识的提交`R`的祖先,则可以进行快进。
这是一个非快进式的获取更新的示例。我有一个 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更新了我的origin/nextorigin/puorigin/todo。其中两个更新是快进操作:例如,提交d8fdbe21b5(我的旧的origin/next)是95628af9bb(我的更新的origin/next)的祖先。但是,我的旧的origin/pu b6268ac8c6不是465df7fb28的祖先。我的Git无论如何都会更新我的origin/pu,失去了访问提交b6268ac8c6的权限(除非通过reflogs)。
为了宣布这个事实,可以使用git fetch命令:
  1. 在行前加上+
  2. 在更新中打印三个点,而不是两个;以及
  3. 在行尾添加(forced update)
由于对origin/nextorigin/todo的更改是快进操作,因此我的Git没有额外提及它们。

关于 git mergegit rebase 如何工作的非常深入的解释。这句话 "Git really just does a git checkout of commit R while moving the branch name master so that it points to commit R (and leaving HEAD attached to branch name master)." 完美地解释了第三个提交图。当你说 "The contents—the saved snapshot—of your new merge commit X match those of your commit R" 时,我可以确认你的意思是合并提交 X 与远程分支提交 R 相同吗?在合并提交 xR 之间,有哪些后续 Git 操作的行为不同的例子? - bit
@MyWrathAcademia:(1)我的意思是在XR中的将完全匹配。树或已保存的快照是当你检出特定提交时在索引和工作树中得到的文件集合。显然,提交本身在其他方面并不匹配:X有两个父节点,并且可能有不同的日期和时间戳。 - torek
当git push无法以这种方式移动分支时,通常会从服务器收到拒绝。这就是为什么当远程分支与本地主分支分歧时(即由于远程分支与跟踪它的本地分支分歧而无法快速转发远程分支)您总是需要git pull的原因吗?您能否澄清以下句子“与!拒绝(非快进)不同,git fetch注释是(强制更新)。 (好吧,这是三个(!)git fetch注释之一。)”,这很令人困惑。 - bit
考虑例如 git push . x:y,其中 xy 都是分支名称。 . 作为远程的名称使得 Git 调用 自身,因此它处理事务的两个方面。你的 Git 首先向自己提供任何你还没有的提交 - 显然这是一个无操作!- 然后询问自己:请将分支 y 的哈希 ID 设置为我通过解析分支名称 x 得到的哈希 ID。如果由 y 标识的提交是被要求更新到的提交的祖先,则您的 Git 将遵从自己的请求。 - torek
我不确定我理解你回答中的最后一段话。特别是,我不明白你所说的“标签移动”是什么意思。在那段话中,你说:“*如果在运动之前确定的提交(在本例中为提交H)是要在之后确定的提交(即提交R)的祖先,则可以进行快进。”这是否意味着只有当你合并一个超出你检出分支的分支时才能进行快进,换句话说,合并的分支领先于检出分支,这意味着提交H是R的祖先,因此可以进行快进? - bit
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接