Git Rebase冲突:谁是HEAD?

92

我有一个项目,其中远程代码库拥有主要的开发分支,而我有一个包含实验性分支的fork。在将更改推送到我的fork之前,我需要将来自开发分支的更改进行rebase。操作步骤如下:

git checkout experimentalbranch
git fetch remoterepo
git rebase remoterepo/developmentbranch

这时候,我遇到了冲突。但是,我对这些变化都不太熟悉(我正在将几周的更改进行变基,因为他们没有立即合并我的更改)。而且,这是我第一次进行变基。我更习惯使用合并

在meld中,合并通常是像<<LOCAL||REMOTE>>这样,听起来非常直观。但在变基中,它是<<HEAD||COMMIT MESSAGE>>。谁是HEAD?它是开发分支的HEAD吗?它是开发分支中最新的代码还是其他地方的代码?

2个回答

147

简介 (2018年5月更新)

整个事情基本上至少有点困惑,因为Git让其内部工作直接展现给你。

请注意,在这里我们关注的情况是当您运行以下命令:

git checkout somebranch; git rebase origin/their-branch
暂停变基是为了让你解决合并冲突,然后使用git add添加已解决的冲突,并运行git rebase --continue。如果你使用带有git mergetool或者高级图形化界面的某些合并工具,那么该界面可能会以其他方式自动完成这些步骤,但在底层,它会通过git add和运行git rebase --continue来完成操作。
一开始,HEAD提交是他们的分支,所以如果你使用git checkout --ours或者git checkout --theirs--ours表示theirs——origin/their-branch的最终提交,而--theirs表示yours,即你正在变基的第一个提交。这是日常Git混淆的普遍情况(参见What is the precise meaning of "ours" and "theirs" in git?),并不是导致最初问题的原因。
然而,随后HEAD提交实际上是某些你的提交复制到了他们的最新提交之上一种混合体。现在,你的新提交系列和原始提交发生了冲突。这个冲突通常是由"他们"做的某些事情(在origin/their-branch中改变了的东西)引起的。你仍然需要解决这个冲突。当你这样做时,你可能会在后续提交中看到完全相同的冲突。
再次说明,HEAD或者local--ours变基将你的更改和他们的更改合并而成的一次提交,而另一个提交(remote>>>>>>>--theirs)是你自己的提交,变基正在尝试将其复制到HEAD之上。

更长的内容

当合并(包括内部重复的“合并”操作,即变基)时,涉及到两个“头”(两个特定分支的末端)。我们称这些为your-branchorigin/their-branch
              G - H --------      <-- HEAD=your-branch
            /               \
... - E - F                   M   <-- desired merge commit [requires manual merge]
            \               /
              I - J - K - L       <-- origin/their-branch

这一点通常是(并不令人惊讶地)令人困惑的,尽管标记得很清楚。

更糟糕的是,git在合并时使用--ours--theirs来表示两个头提交,其中“ours”是您运行 git merge 时所在的一个头提交(提交H),而“theirs”则是他们的提交(提交L)。但是当您进行变基时,这两个头会相反,因此“ours”是您正在将其变基到的头提交,即他们的更新代码,而“theirs”则是您当前正在变基的提交,即您自己的代码。

这是因为变基实际上使用一系列的 cherry-pick 操作。您开始的图片与合并时大致相同:

              G - H           <-- HEAD=your-branch
            /
... - E - F
            \
              I - J - K - L   <-- origin/their-branch

Git 需要做的是 复制 提交记录 GH 的效果,也就是说,使用 git cherry-pick 命令复制提交记录 G,然后再复制一次提交记录 H。但为了做到这点,Git 必须先在内部(使用 "detached HEAD" 模式)切换 到提交记录 L

              G - H           <-- your-branch
            /
... - E - F
            \
              I - J - K - L   <-- HEAD, origin/their-branch

现在它可以通过比较提交记录 FG 的树(以查看您进行了哪些更改),然后比较 FL(以查看您的部分工作是否已经在 L 中)并将任何尚未在 L 中的更改添加进来。这是一个内部的“合并”操作。

              G - H           <-- your-branch
            /
... - E - F                   G'   <-- HEAD
            \               /
              I - J - K - L   <-- origin/their-branch
如果合并不顺利,HEAD仍然停留在提交L (因为提交G'尚不存在)。 因此,是的,HEAD是他们开发分支的头 - 至少现在是这样。
一旦存在G的副本,HEAD就会移动到G',并且Git会尝试以相同的方式复制更改(差异G vs H,然后差异F vs G',并合并结果):
              G - H           <-- your-branch
            /
... - E - F                   G' - H'   <-- HEAD
            \               /
              I - J - K - L   <-- origin/their-branch

如果合并失败并需要帮助,你将会发现 HEAD 指向 G' 而不是 H',因为 H' 还不存在。

一旦所有的合并都成功并提交了 G'H' ,git 将从提交 H 中删除标签 your-branch,并将其指向提交 H'

              G - H
            /
... - E - F                   G' - H'   <-- HEAD=your-branch
            \               /
              I - J - K - L   <-- origin/their-branch

现在您已经重新定位,并且HEAD再次符合您的预期。 但是在变基过程中,HEAD可能是其分支末端(提交L),也可能是复制并附加到其分支末端之后的新提交之一;而--ours表示在 L 的末尾增长的分支,而--theirs则表示被复制的提交(上述的GH)。

(这基本上是git揭示了它执行操作的原始机制,这在git中经常发生。)


3
基本上,在第一个操作中,“HEAD”是“他们的分支”的“HEAD”,在后续的操作中,“HEAD”现在是我新添加的提交。 - Joseph
2
可以,但可能会让人感到困惑,因为它们是新附加到你“正在成长”的分支上的提交,该分支还没有名称。在上面的图纸中,它们位于中间行,而不是顶部或底部行。 - torek
8
当我在冲突的文件中遇到<<<<<<< HEAD时,我仍然不确定这一部分是我的还是他们的?答案的复杂性是否意味着它可以被改述为“这取决于具体情况”? - dumbledad
2
@dumbledad:是的,这要取决于你是在执行git merge还是git rebase(或者git cherry-pickgit revert)。在所有情况下,当实际命令运行时,HEAD是任何时候的HEAD,因此最令人困惑的是,对于交互式rebase,git rebase从一个正在增加新提交项的目标分离的HEAD中运行git cherry-pick(用于非交互式)或git am - torek
1
@nafg:这很棘手。例如,假设rebase已经成功地复制了您的前三个提交(共四个):那么HEAD是您第三个提交的副本。合并冲突是复制您的第四个提交的结果。但显然,您的第四个提交在原始的四个提交序列中也很好地应用了。冲突从哪里来?它必须来自某个早期(HEAD的祖先)提交:不是您已经复制的三个提交之一,而是在那个点之前的某个提交。但它可能出现在任何地方。 - torek
显示剩余18条评论

9

定义

在本节中,我们将看到响应中要求的定义:

谁是HEAD?

HEAD: 您存储库当前所在的提交。大多数情况下,HEAD指向分支中的最新提交,但不一定是这种情况。HEAD实际上只意味着“我的存储库目前正在指向什么”。

如果HEAD所指的提交不是任何分支的末端,这称为“分离头”。

它是否为开发分支的HEAD?

在进行合并或变基的同时,HEAD立即传递以指向已创建或重构的提交因此将指向开发分支

在git bash中,我们可以查看HEAD的情况,列出提交:

# Normal commit list
git log
# List of commit in a single line
git log --oneline 
# All commits graphically-linear (Recommended as alias)
git log --all --graph --decorate --oneline

实践

在本节中,我们将了解用户执行某些操作的_方式_

当用户进行以下操作时:

# Command to change from the branch to the current one to experimentalbranch
git checkout experimentalbranch
# Command that traverses the typical workflow to synchronize its local repository with the main branch of the central repository (remoterepo)
git fetch remoterepo
# git fetch origin
# git fetch origin branch:branch
# With the command git rebase, you can take all the changes confirmed in one branch (remoterepo), and reapply them over another developmentbranch
git rebase remoterepo/developmentbranch

到这个时候,我遇到了冲突。然而,我对这些变化都不熟悉(我正在重新定位几周前的更改,因为他们没有立即合并我的更改)。此外,这是我第一次进行rebase操作。我更习惯于使用merge操作。
分支的合并有两种方式:
- git merge - git rebase 注意:
我们将使用以下树形结构作为示例。
* a122f6d (HEAD -> remoterepo) Commit END
* 9667bfb Commit MASTER
| * b9bcaf0 (origin/experimentalbranch, experimentalbranch) Commit 3
| * 110b2fb Commit 2
| * e597c60 Commit 1
|/
* 0e834f4 (origin/remoterepo) First commit

git合并

最常见的形式是git merge,它在每个分支的最后两个快照和两者的共同祖先之间执行三条带的融合,创建一个包含混合更改的新提交。

例如:

git checkout remoterepo
git merge experimentalbranch

它将会产生以下结果:
*   003e576 (HEAD -> remoterepo) Merge branch 'experimentalbranch' in remoterepo
|\
| * b9bcaf0 (origin/experimentalbranch, experimentalbranch) Commit 3
| * 110b2fb Commit 2
| * e597c60 Commit 1
* | a122f6d Commit END
* | 9667bfb Commit MASTER
|/
* 0e834f4 (origin/remoterepo) First commit

git rebase

git rebase 的基本作用是将一个分支中确认的所有更改逐个收集起来,并重新应用到另一个分支上

使用rebase可以帮助我们避免在应用于尚未上传到任何远程存储库的本地提交时出现冲突。如果您对后者不小心处理,而合作伙伴使用了受影响的更改,则肯定会遇到问题,因为这些类型的冲突通常难以修复

例如:

git checkout remoterepo
git rebase experimentalbranch


* f8a74be (HEAD -> remoterepo) Commit END
* 4293e9d Commit MASTER
* b9bcaf0 (origin/experimentalbranch, experimentalbranch) Commit 3
* 110b2fb Commit 2
* e597c60 Commit 1
* 0e834f4 (origin/remoterepo) First commit

什么是 origin?

origin: git 给你的主要远程仓库默认的名称。你的电脑有自己的仓库,你很可能会推送到一些你和你的同事共同推送的远程仓库。那个远程仓库通常被称为 origin,但它不一定是。


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