13|合并冲突——理解 ours 与 theirs
出现冲突并不表示 Git 出了问题,只是两个分支修改了同一处内容,Git 无法自动判断应该保留哪一边。本章会在两条练习分支上制造两处可控冲突,再用 Diffview 的三方界面逐块解决。
开始前先确认工作树干净
全程只碰 README.md。开始前务必保存所有文件,并确认 git status --short 没有输出。
本章核心新按键
| 按键 | 效果 |
|---|---|
] x / [ x | 下一个 / 上一个冲突区 |
Space c o | 当前冲突选择 ours |
Space c t | 当前冲突选择 theirs |
Space c b | 当前冲突只保留共同祖先 base 内容 |
Space c a | 按 ours → base → theirs 拼接当前冲突内容并删除标记 |
d x | 删除整个当前冲突区 |
主练习只使用前四项中的 ours 与 theirs。其他按键先了解用途,不要求本章全部记住。
开始前检查
按 F1 → wall →Enter → Enter,打开终端运行:
git status --short
git branch --show-current
git config --local merge.conflictStyle diff3
git config --local --get merge.conflictStyle
第一条应没有输出。第二条会告诉你当前基线分支叫什么,可能是 main、master 或别的名字。本练习会从它分出两条教学分支,原基线不会被改写。最后一条应输出 diff3,表示本仓库会把共同祖先内容也写进冲突标记。这项设置只作用于当前练习仓库。
工作流一:制作 ours 版本
在终端运行:
git switch -c tutorial-ours
隐藏终端,打开 README.md。

搜索描述行:
/A tiny
Enter按 c c,输入:
A tiny task tracker for focused terminal sessions.按 Esc。

再搜索 Quick start 的命令行:
/Run
Enter按 c c,输入:
Run the CLI with `PYTHONPATH=src python -m pocket_tasks.cli`.按 Esc,保存文件。打开终端运行:

git add README.md
git commit -m "describe a focused workflow"
现在 tutorial-ours 比共同基线多了一个提交。
工作流二:从共同基线制作 theirs 版本
终端运行:
git switch -c tutorial-theirs HEAD~1
这条命令从 ours 提交的父提交创建新分支,因此两边拥有同一个共同祖先。
切换分支会改写磁盘文件。回到 README 后,如果屏幕仍显示上一分支的句子:
- 按
F1。 - 输入
edit。 - 连按两次
Enter,重新读取磁盘版本。

将同两行改成另一套文案。
描述行:
A friendly task tracker for terminal-loving humans.
Quick start 行:
Start it with `PYTHONPATH=src python -m pocket_tasks.cli`.
保存,终端运行:
git add README.md
git commit -m "describe a friendly workflow"
现在两条分支分别修改了相同的两行,因此合并时会产生冲突。

工作流三:制造冲突
终端运行:
git switch tutorial-ours
git merge tutorial-theirs
Git 应该会报告 README.md 存在 content conflict,并暂停合并。这正是本章需要的练习状态。
隐藏终端。如果 README 仍显示切换前的内容,执行一次 F1 → edit → Enter → Enter。
此时直接打开文件会看到类似标记:

<<<<<<< HEAD
A tiny task tracker for focused terminal sessions.
||||||| 共同祖先提交
A tiny task tracker for terminal-loving humans.
=======
A friendly task tracker for terminal-loving humans.
>>>>>>> tutorial-theirs
也可以手工删除冲突标记,但本节使用 Diffview 三方界面,以便直接比较各版本内容。
为什么练习启用 diff3
中间以 ||||||| 开头的段落就是 BASE,也就是两个分支修改前的共同祖先。Diffview 的 b 与 a 操作会读取 LOCAL 文件中的这段冲突标记:
- 有 BASE 时,
Spacecb会只留下共同祖先内容; - 有 BASE 时,
Spaceca会按 ours → base → theirs 的顺序拼接三段内容,再删除标记; - 缺少 BASE 标记时,
b会把 BASE 当作空内容,当前冲突可能被清空; - 缺少 BASE 标记时,
a会拼接现有的 ours 与 theirs; - BASE 标记存在、段落内容恰好为空时,
b得到的结果同样为空。
大写 B 与 A 会把相应操作应用到当前文件的所有冲突,因此影响范围更大。在真实项目中使用 b/B 或 a/A 前,先确认 LOCAL 中确实存在 ||||||| BASE 标记。
工作流四:在三方界面逐块解决
按:
Space g s
Diffview 发现未合并文件后会启用 merge tool。默认 diff3_horizontal 布局把三个版本横向排开:
┌──────── OURS ────────┬──────── LOCAL ────────┬─────── THEIRS ───────┐
│ 当前分支版本 │ 磁盘上的合并结果 │ 传入分支版本 │
│ A │ B,可编辑 │ C │
└──────────────────────┴────────────────────────┴───────────────────────┘
- OURS 是当前分支
tutorial-ours; - THEIRS 是正在合入的
tutorial-theirs; - LOCAL 是最终要保存并暂存的工作文件;
- BASE 是两边共同祖先,默认三窗布局没有单独展示它,但选择键仍可取用。
用 Ctrl-w 加 h/l 移到标题含 LOCAL 的中间窗口。
第一处冲突选 ours
- 按
gg到顶部。 - 按
]x跳到第一处冲突。 - 确认三窗显示的是 README 描述行。

- 按
Spaceco。
LOCAL 中这一块应变成:
A tiny task tracker for focused terminal sessions.
冲突标记随之消失。
第二处冲突选 theirs
- 再按
]x。 - 光标来到 Quick start 行的冲突。

- 按
Spacect。
LOCAL 中这一块应变成:
Start it with `PYTHONPATH=src python -m pocket_tasks.cli`.
最终结果分别采用了两个分支中的一处修改,README 的两处冲突都已经解决。

另外三种选择何时有用
| 按键 | 适合的情况 |
|---|---|
Space c b | 两边的修改都不合适,先恢复共同祖先内容再重写 |
Space c a | 按 ours → base → theirs 拼接后,再手工去重与整理 |
d x | 整块内容都应删除 |
这三项会直接修改 LOCAL。拿不准时先阅读三窗,再动手。
保存、暂存、完成合并
确认焦点位于 LOCAL 窗口,然后:
- 按
F1。 - 输入
write。

- 连按两次
Enter。 - 按
Spacee聚焦 Diffview 文件面板。 - 把光标放在
README.md上,按小写s暂存整份已解决文件。

- 按
F1→DiffviewClose→Enter→Enter。

打开终端运行:
git status
git diff --cached --check
PYTHONPATH=src python -m unittest -v
Git 应提示冲突已经解决、合并仍在进行。测试通过后完成合并提交:

git commit -m "merge tutorial conflict branches"
git status --short
最后一条应没有输出。当前分支仍是 tutorial-ours,并包含一个合并提交。
真实项目中也可在暂存完成后运行 git merge --continue。Git 若打开提交信息编辑器,确认信息后保存退出即可。
重新开始冲突练习
只要合并提交还没创建,就可以关闭 Diffview,在终端运行:
git merge --abort工作树会恢复到合并开始前的 tutorial-ours。随后再次执行 git merge tutorial-theirs,即可重新生成冲突。
高风险:单次应用全部
下面这些映射会一次处理当前文件里的全部冲突:
大写映射会处理整份文件
大小写只差一个 Shift,但影响范围会从当前冲突扩大到整个文件。优先使用小写逐块处理;只有确认所有冲突都采用同一策略时,才考虑大写版本。
| 按键 | 效果 |
|---|---|
Space c O | 全文件选择 ours |
Space c T | 全文件选择 theirs |
Space c B | 每处冲突都只保留共同祖先 base 内容 |
Space c A | 每处冲突都按 ours → base → theirs 拼接并删除标记 |
d X | 删除全文件的全部冲突区 |
rebase 中 ours 与 theirs 的含义
普通 merge 中:
- ours 通常是当前所在的分支;
- theirs 通常是正在合入的分支。
rebase 会把提交逐个应用到目标分支上。此时 Git 所说的 ours 往往是 rebase 目标分支的状态,theirs 则往往是当前正在应用的提交,这与普通 merge 时的直觉可能相反。
所以遇到 rebase 冲突时:
- 先读 Diffview 窗口标题;
- 再看两边具体内容;
- 确认版本身份后才按
Spaceco或Spacect。
rebase 中不要凭 ours/theirs 的名称判断版本
不要只根据 ours/theirs 的名称判断版本身份,应先阅读窗口标题和实际内容。
冲突处理固定流程
以后可以按下面的固定流程处理:
- 终端执行 merge 或 rebase。
- 保存其他工作,按
Spacegs。 ]x逐块跳。- 每块选 ours、theirs、base、all,或手工编辑 LOCAL。
- 保存 LOCAL。
- 文件面板按
s暂存已解决文件。 - 关闭 Diffview。
- 跑测试和
git diff --cached --check。 - 终端完成 merge 或 rebase。
下一章进入 14:会话与日常工作流。我们会把编辑、LSP、测试、Git 审查、会话保存和恢复串成完整的日常开发流程。