未记录事实核验 · 状态:现行内容

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用范围与待维护事项

本文适用于已掌握提交、分支和远程协作的读者,建议在可丢弃的练习仓库中操作;主流程针对文本文件的普通 merge 冲突,不覆盖二进制、删除/修改等冲突。开始前请提交或妥善暂存未提交改动,abort 不保证恢复复杂的原有工作区。rebase 示例仅用于未共享的个人提交,共享分支须先遵循团队约定。SQL 为教学片段,无完整表结构与测试数据;删除标记并暂存不代表业务正确,合并后仍需测试过滤、关联和聚合结果。

Git 冲突解决 - 合并冲突处理与最佳实践

abstract 本章概要

  • 理解合并冲突产生的原因
  • 掌握冲突标记的阅读方式
  • 学会手动解决冲突的完整步骤
  • 了解减少冲突的最佳实践

冲突是什么,为什么会发生

合并冲突不是 bug,也不是有人出错了,它是两个人同时改了同一个地方的自然结果,Git 不知道该保留谁的版本,只能请你来做仲裁。

典型场景:

远程 main(你同事 push 的):
  ...
  WHERE order_date >= '2024-01-01'
  AND order_status = 'completed'    ← 同事加了状态过滤
  ...

你的本地分支(你改的):
  ...
  WHERE order_date >= '2024-01-01'
  AND user_type = 'VIP'             ← 你加了用户类型过滤
  ...

你和同事都改了同一行附近的代码,Git 无法自动判断是应该保留哪个条件,或者两个都要保留,于是产生了冲突。


哪些操作会触发冲突

  • git merge:合并分支时
  • git pull:拉取远程代码时(本质是 fetch + merge/rebase)
  • git rebase:变基操作时
  • git cherry-pick:挑选提交时

冲突标记:读懂 Git 的语言

当冲突发生时,Git 会修改产生冲突的文件,插入特殊标记:

SELECT
  user_id,
  SUM(order_amount) AS total_amount
FROM orders
WHERE order_date >= '2024-01-01'
<<<<<<< HEAD
  AND user_type = 'VIP'
=======
  AND order_status = 'completed'
>>>>>>> feature/add-status-filter
GROUP BY user_id

理解这三段:

标记 含义
<<<<<<< HEAD 冲突开始,HEAD 是你当前所在的位置
======= 分隔线
>>>>>>> feature/add-status-filter 冲突结束,这是来自哪个分支的内容

在本文的普通 merge 示例中,======= 以上是当前分支的内容,以下是被合并分支的内容。rebase 时不能简单按“我的/对方的”判断:上侧通常是变基目标加上已重放的提交,下侧是当前正在重放的提交;编辑器的 Current/Incoming 也应按这一上下文理解。


解决冲突的完整步骤

第一步:发现冲突

# 示例明确使用 merge 方式拉取;存在重叠修改时可能发现冲突
git pull --no-rebase
# 输出:
# Auto-merging sql/orders_etl.sql
# CONFLICT (content): Merge conflict in sql/orders_etl.sql
# Automatic merge failed; fix conflicts and then commit the result.

# 查看哪些文件有冲突
git status
# 输出:
# Unmerged paths:
#   (use "git add <file>..." to mark resolution)
#       both modified: sql/orders_etl.sql

第二步:打开冲突文件

用你熟悉的编辑器打开有冲突的文件,找到所有 <<<<<<< 标记。

如果用 VS Code,它会直观地高亮显示冲突区域,并提供按钮:

  • "Accept Current Change":保留你的版本
  • "Accept Incoming Change":保留对方的版本
  • "Accept Both Changes":保留两者(上下拼接)
  • "Compare Changes":并排对比

第三步:手动决策

对于上面的例子,正确的解决方式可能是:两个条件都需要,应该合并:

-- 解决后的结果(删掉所有冲突标记,保留正确内容)
SELECT
  user_id,
  SUM(order_amount) AS total_amount
FROM orders
WHERE order_date >= '2024-01-01'
  AND user_type = 'VIP'          -- 保留 VIP 过滤
  AND order_status = 'completed' -- 同时保留状态过滤
GROUP BY user_id

你需要根据业务逻辑来决定:

  • 保留我的版本
  • 保留对方的版本
  • 合并两者
  • 写一个全新的版本

warning 解决冲突前必须理解业务含义

不要机械地"保留所有"或者"保留最新的"。数据代码的冲突往往有业务含义,必须理解每个改动的目的,才能做出正确的合并决策。不确定的时候,和改动的作者沟通一下再动手。

第四步:标记冲突已解决

# 把解决好的文件加入暂存区
git add sql/orders_etl.sql

# 确认所有冲突都解决了
git status
# 此时 sql/orders_etl.sql 从 "Unmerged" 变成了 "Changes to be committed"

第五步:完成合并

# 以下两种提交方式二选一,不要依次执行
# 方式一:使用 Git 自动生成的 merge commit 信息模板
git commit
# 方式二:直接指定更有说明性的信息
git commit -m "merge: 合并feature/add-status-filter,同时保留VIP和状态过滤条件"

Rebase 时的冲突处理

Rebase 时产生的冲突处理稍有不同,因为 rebase 是一个提交逐个应用的过程,可能产生多轮冲突。

git pull --rebase
# 产生冲突...

# 1. 解决冲突(同上:编辑文件,删除冲突标记)

# 2. 暂存解决后的文件
git add sql/orders_etl.sql

# 3. 继续 rebase(不是 commit!)
git rebase --continue

# 如果下一个提交也有冲突,重复步骤 1-3

# 如果中途想放弃 rebase,回到 rebase 前的状态
git rebase --abort

使用 merge 工具

如果文件很长,冲突很多,手动处理容易遗漏。可以用 merge 工具:

# 使用配置的 merge 工具打开所有冲突文件
git mergetool

# 查看当前配置的 merge 工具
git config merge.tool

配置 VS Code 为 merge 工具

git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'

配置 vimdiff

git config --global merge.tool vimdiff
git config --global merge.conflictstyle diff3

merge.conflictstyle diff3 会在文本冲突标记中加入公共祖先区段,展示当前侧、公共祖先和传入侧的内容,并不直接生成三列界面;vimdiff 的窗口布局由合并工具决定。下面是普通 merge 的示意:

<<<<<<< HEAD
  AND user_type = 'VIP'
||||||| merged common ancestors    ← diff3 模式会显示公共祖先(改之前)
  AND 1=1
=======
  AND order_status = 'completed'
>>>>>>> feature/add-status-filter

数据项目中冲突的常见类型

类型一:SQL 逻辑冲突(最常见)

两人同时修改了同一个 SQL 文件的过滤条件、JOIN 关系或 GROUP BY 字段。

处理方式:理解两个改动的业务背景,判断是否都需要,合并时可能需要调整整个查询的逻辑。

类型二:配置文件冲突

调度配置、连接配置等文件被多人修改:

<<<<<<< HEAD
  schedule: "0 2 * * *"    # 你改成了凌晨2点
=======
  schedule: "0 3 * * *"    # 同事改成了凌晨3点
>>>>>>> feature/adjust-schedule

处理方式:和相关人员沟通,确认最终的调度时间。

类型三:数据模型冲突

dim_user.sql 建表语句中,两人分别加了不同的字段:

<<<<<<< HEAD
  user_level VARCHAR(20),   -- 你加的用户等级字段
=======
  user_channel VARCHAR(50), -- 同事加的渠道来源字段
>>>>>>> feature/add-channel

处理方式:通常是两个都需要,合并保留:

  user_level   VARCHAR(20),
  user_channel VARCHAR(50),

预防冲突的最佳实践

冲突是可以减少的,好的工作习惯能让你少踩很多坑。

实践一:频繁 pull,小步提交

# 每天开始工作前,先更新代码
git pull --rebase

# 功能完成一部分就提交,不要攒着
# 小步提交便于定位、审查和处理冲突;是否发生冲突仍取决于改动重叠及分支差异

实践二:沟通先于修改

改一个大家都在用的核心文件(如 dim_user.sql、ods_layer.sql)前,先在群里说一声。

"我今天要改 dim_user.sql,加用户等级字段,大概改以下几处,有人也在改这个文件吗?"

实践三:文件职责单一

把大文件拆分成职责单一的小文件,每个文件只负责一件事。这样两个人修改的文件重叠概率大大降低。

bad:  all_etl.sql(所有 ETL 逻辑都在这)

good:
  etl_orders.sql
  etl_users.sql
  etl_products.sql
  etl_payments.sql

实践四:功能分支要短命

分支存活时间越长,和主干差异越大,合并时冲突越多。

  • 功能分支生命周期:可将不超过 3 天(小功能)到 2 周(复杂功能)作为团队讨论的经验目标,并非通用上限
  • 开发期间定期同步主干;未共享的个人提交可使用 rebase,共享分支应先遵循团队约定,避免擅自改写他人依赖的历史
# 工作区干净且功能分支提交尚未共享时,将其变基到最新主干
git switch feature/my-feature
git fetch origin
git rebase origin/main

实践五:.gitignore 排除自动生成文件

自动生成的文件(如 PyCharm 的 .idea 文件夹、Jupyter 的 .ipynb_checkpoints)经常产生无意义的冲突。用 .gitignore 排除它们。详见 07-Git进阶技巧。


处理冲突时的心理准备

tip 冲突不是灾难

初学者看到冲突往往会慌。其实冲突很正常,处理起来也有固定套路:找到标记 → 理解两边的改动 → 决策 → 删除标记 → add → commit。按步骤来,不要急。

tip 不确定就问

如果你不理解对方的改动是什么意思,不要猜,直接问那个人。几分钟的沟通能避免你做出错误的合并决策,否则引入了业务 bug 比冲突本身麻烦多了。


冲突解决快速参考

# 查看有哪些文件有冲突
git status

# 列出尚未解决冲突的文件名;查看具体差异可用 git diff -- <文件路径>
git diff --name-only --diff-filter=U

# 解决冲突(编辑文件,删除冲突标记)后
git add <resolved-file>

# 所有冲突解决后
git commit  # 完成 merge

# 或者(rebase 时)
git rebase --continue

# 想放弃,恢复到 merge/rebase 前
git merge --abort
git rebase --abort

小结

步骤 操作
1. 发现冲突 git status 查看哪些文件
2. 打开文件 编辑器看 <<<<<<< 标记
3. 理解冲突 搞清楚两边改动的意图
4. 做决策 保留谁的/合并/重写
5. 删除标记 删除所有 <<< === >>>
6. 标记完成 git add <file>
7. 提交 git commit


info 前置知识

  • 远程仓库协作 - 冲突通常发生在 pull 或 merge 时,需要理解远程协作场景
  • 分支管理 - 理解为什么两个分支改了同一处会产生冲突

掌握检查

  • [ ] 能识别冲突标记的三个区域:<<<<<<< HEAD(当前分支内容)、=======(分隔线)、>>>>>>> branch(来源分支内容)
  • [ ] 解决冲突后知道完整的收尾流程:删除所有冲突标记 → git add → git commit(不需要写 -m,Git 会自动填写合并信息)
  • [ ] 知道 git merge --abort 可以放弃正在进行的合并,回到合并前的状态
  • [ ] 理解 rebase 和 merge 在冲突处理上的区别:rebase 的冲突需要 git rebase --continue 推进
  • [ ] 知道减少冲突的实践方法:定期同步主干、协调对共享文件的修改;git pull --rebase 同步的是当前分支配置的上游,不一定是 main,共享提交使用 rebase 前须遵循团队约定

实践练习

基础题:手动制造一个冲突:在 main 和 feature 分支上修改同一文件的同一行,然后合并,练习识别冲突标记、做出决策(保留哪一边还是合并两者),完成完整的冲突解决流程。

进阶题:制造一个 SQL 文件的冲突:两个分支从同一提交出发,修改同一行或重叠的 SQL 代码区段,合并时练习处理冲突,模拟真实业务场景中和同事沟通后做出合并决策的过程。若一个只改 WHERE 条件、另一个只改不重叠的 JOIN 逻辑,Git 可能自动合并;此时仍需检查组合后的业务逻辑。


下一步:Git进阶技巧 - 学习 stash、rebase、tag 等进阶技巧,提升日常工作效率。

上一步:远程仓库协作 | 返回:Git学习路线图