Git 合并冲突 HEAD 和 incoming 分别是谁的
Git 合并冲突中 HEAD 和 incoming 分别代表哪一方
当你运行 git merge 或 git rebase 遇到冲突时,Git 会在冲突文件中插入特殊标记,展示两个不同来源的代码块。这些标记里最常见的就是 <<<<<<< HEAD 和 >>>>>>> <branch-name>,而 incoming(或称为 theirs)则是许多开发工具和文档中用来指代“被合并过来的那一边”的术语。
本教程将用最直观的方式,帮助你彻底弄清楚:冲突标记里的 HEAD 到底是谁,incoming 又是谁,以及在不同 Git 操作下它们如何互换角色。
核心概念:HEAD 与 incoming 的定义
在 Git 中,HEAD 是一个指针,它始终指向你当前所处的提交(commit)。大多数情况下,HEAD 指向当前分支的最新提交。
当 Git 需要将两个不同的改动合并到同一个文件时,它会用冲突标记框出两个版本:
<<<<<<< HEAD
当前分支上的内容
=======
被合并分支上的内容
>>>>>>> feature-branch
<<<<<<< HEAD下面的内容:属于 当前分支,也就是你正在操作的分支(例如main)。它是你工作目录的“现状”。>>>>>>> feature-branch下面的内容:属于 被合并进来的分支(这里例子中是feature-branch)。在许多图形界面工具和教程中,这一侧被称为 incoming 或 theirs,意为“外部传入的修改”。
简单记忆:
- HEAD = ours(我们的),即你当前所在的、正在合并目标的分支。
- incoming = theirs(他们的),即你尝试合并进来的分支。
场景一:git merge 时的 HEAD 与 incoming
这是最常见的合并场景。假设你现在在 main 分支,想把 feature 分支合并进来:
git checkout main
git merge feature
当发生冲突时,文件中会显示:
<<<<<<< HEAD
这里是 main 分支上的代码
=======
这里是 feature 分支上的代码
>>>>>>> feature
- HEAD 就是
main分支的内容(因为你正处于main)。 - incoming 就是
feature分支的内容(你试图合并进来的那个分支)。
此时,如果你使用命令选择某一侧:
git checkout --ours <file>会保留main的版本(HEAD)。git checkout --theirs <file>会保留feature的版本(incoming)。
场景二:git rebase 时角色会互换——这是最容易混淆的地方!
git rebase 的工作方式与 git merge 完全不同。它会将当前分支的提交“搬到”目标分支的顶端,因此在 rebase 过程中,HEAD 所指的上下文会发生变化。
假设你在 feature 分支,想把它 rebase 到 main 上:
git checkout feature
git rebase main
此时 Git 会:
- 将
feature分支独有的提交临时保存。 - 将
HEAD移动到main分支的最新提交(即你正在“重放”提交的基准)。 - 逐个应用
feature的提交。
在这个过程中如果发生冲突,冲突标记会变成:
<<<<<<< HEAD
这里是 main 分支上的代码(你当前 rebase 的基底)
=======
这里是 feature 分支上的代码(你正在应用的补丁内容)
>>>>>>> 你的提交信息(正在重放的 commit)
注意角色的反转:
- 现在 HEAD 代表的是 你正在重放到其上的基底分支,也就是
main的内容。但在merge思维里,你可能会觉得它是“别人的”。 - 而 incoming(标记右侧或
>>>>>>>后面的内容)是 你自己的 feature 分支的内容,也就是你正在重放的补丁。
在 rebase 冲突中,记住这个反直觉的关系:
HEAD此时相当于merge场景里的theirs(因为基底是你要合并进的目标的上游代码)。incoming(右侧的更改)相当于merge场景里的ours(因为那是你自己分支上的工作)。
因此,如果你在 rebase 冲突中想要保留自己 feature 分支的代码,你应该选择 incoming(右侧),而不是 HEAD。很多初学者在这里选错,导致自己的改动被上游覆盖。
如何正确分辨并解决冲突
无论使用哪种操作,都可以通过以下两条规则来记忆:
| 操作 | HEAD 代表…… | incoming(theirs)代表…… |
|---|---|---|
git merge |
当前分支(你 merge 时所在的分支) | 被合并的分支(你指定的那个分支) |
git rebase |
被重放到其上的基分支(上游分支) | 你正在应用补丁的分支(你自己的提交) |
在命令行中解决:
- 使用
git checkout --ours <file>保留 HEAD 一侧的内容。 - 使用
git checkout --theirs <file>保留 incoming 一侧的内容。 - 或者手动编辑文件,删除冲突标记,保留所需代码。
在 VS Code 等图形工具中:
通常会显示两个选项:
- Accept Current Change(接受当前更改):对应 HEAD。
- Accept Incoming Change(接受传入更改):对应 incoming/theirs。
- 在 rebase 时,含义会随角色变化,务必根据上面表格判断哪一侧是你真正想要的。
实践技巧:永远先看清你在哪个分支、做什么操作
遇到冲突不要慌,按以下步骤确认:
- 运行
git status查看你是否处于合并状态(You have unmerged paths)或变基状态(rebase in progress)。 - 用
git branch或cat .git/HEAD确认当前 HEAD 指向哪个分支或提交。 - 回忆你输入的命令:是
git merge还是git rebase?哪个是目标分支? - 打开冲突文件,根据标记文字下的分支名或提交信息判断:
<<<<<<< HEAD下方是当前基底的内容;>>>>>>>后面跟着的分支名或提交信息是传入的来源。 - 决定保留 HEAD 还是 incoming,或两者结合后手动编辑保存。
总结
- HEAD 永远是你当前工作的“基础点”。在
merge里是你的目标分支;在rebase里是你要把 commit 移动到的上游分支。 - incoming / theirs 是“从外部加入”的改动。在
merge里就是那个被合并的分支;在rebase里则是你自己的补丁分支。 - 角色互换是理解冲突的关键:merge 的 ours 是 rebase 的 theirs,反之亦然。
- 掌握这个逻辑后,再也不会在冲突里删错代码。
希望这篇教程能帮你建立清晰的冲突思维模型。把这张对照表收藏起来,下次遇到冲突对照一下,很快就能熟练应对。