◆

Git 合并后报错 Merge conflict marker encountered 解决

2023-02-02 01:370

场景:使用 git megre 合并代码后,项目中有几个文件一直报 Merge conflict marker encountered

解决方法:

  1. 对这几个文件任意加一下空格等,然后自动重新编译后就好了
  2. 切换分支再切回来
  3. 删除 node_modules/.cache 文件

先分清是哪一种情况

这个报错来源有两个,处理方式完全不同,得先判断清楚。

情况一:文件里真的还留着冲突标记。 也就是这三行还在源码里:

<<<<<<< HEAD
自己分支的代码
=======
对方分支的代码
>>>>>>> feature/xxx

这是解决冲突时手动改漏了,或者编辑器的合并工具点了「接受」但某一块没处理完。这种情况下报错是准确的,上面那三个办法都无效,必须真的把标记删掉。

情况二:文件已经干净了,但报错还在。 这就是原文那三个办法针对的场景——冲突已经解决,git diff 看不到任何标记,但 ESLint / Webpack / Vite 还在报。这是缓存问题,报错的是过期的编译结果。

一条命令就能区分:

# 在整个 src 里搜冲突标记(用 7 个字符精确匹配,避免误命中注释)
grep -rn "^<<<<<<< " src/
grep -rn "^=======quot; src/
grep -rn "^>>>>>>> " src/

# 或者让 git 直接列出还未解决的文件
git diff --name-only --diff-filter=U

--diff-filter=U 里的 U 是 unmerged,这条命令列出的就是 git 认为冲突尚未解决的文件。它是最权威的判据——如果它有输出,说明是情况一。

情况二:为什么缓存会留着旧内容

因为 Webpack / Vite / ESLint 的缓存 key 大多基于文件路径 + mtime + size,而不是内容哈希。git merge 或 git checkout 改写文件时,如果新内容和缓存记录的 size 恰好相同,或者 mtime 精度不足没捕捉到变化,缓存就会误判文件未修改,直接吐出上一次编译的产物——那份产物是带冲突标记时编译失败的结果。

这解释了原文三个办法为什么有效:

  1. 加个空格改变了文件的 size 和 mtime,缓存 key 变了,强制重新编译。
  2. 切分支再切回来让 git 重写文件,同样刷新 mtime。
  3. 删缓存目录是直接治本。

第三种最可靠,具体路径按工具而定:

# Webpack 5 的持久化缓存(Vue CLI 项目)
rm -rf node_modules/.cache

# Vite
rm -rf node_modules/.vite

# ESLint(用了 --cache 才有)
rm -f .eslintcache

# TypeScript 增量编译
rm -f tsconfig.tsbuildinfo

# 一把清干净
rm -rf node_modules/.cache node_modules/.vite .eslintcache *.tsbuildinfo

Vite 项目还要注意浏览器侧的缓存——node_modules/.vite 删了但浏览器里还存着旧的预构建产物,需要硬刷新(Cmd/Ctrl + Shift + R)或者用 vite --force 启动。

情况一:怎么把冲突标记清干净

如果 git diff --diff-filter=U 有输出,正确做法是回到冲突解决流程:

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

# 逐个文件选择一整边(适合 lock 文件、构建产物这类不需要手工合并的)
git checkout --ours pnpm-lock.yaml    # 保留当前分支的版本
git checkout --theirs pnpm-lock.yaml  # 保留合入分支的版本

# 手工改完之后标记为已解决
git add <file>

# 全部解决后完成合并
git commit

要注意 --ours / --theirs 在 merge 和 rebase 里的含义是相反的。merge 时 ours 是你当前所在的分支;rebase 时 git 内部是把你的提交往上游重放,ours 变成了上游分支,theirs 才是你自己的提交。这个反转坑过很多人,rebase 冲突时最好用 git log --merge 或者图形工具确认内容,别只凭参数名。

彻底放弃这次合并回到干净状态:

git merge --abort
# rebase 的话
git rebase --abort

让这类问题少发生

装个 lint 规则把冲突标记拦在提交之前。 ESLint 的 no-irregular-whitespace 管不了这个,专门的规则来自 git 自己的 hook。最简单的是加一条 pre-commit 检查:

# .husky/pre-commit
if git diff --cached --name-only -z | xargs -0 grep -lE '^(<{7}|={7}|>{7})( |$)' 2>/dev/null; then
  echo "存在未解决的冲突标记,提交已拦下"
  exit 1
fi

开启 merge.conflictStyle = zdiff3。 默认的 merge 样式只给两边内容,diff3 会额外标出共同祖先的原始内容,判断该保留什么容易得多。zdiff3 是 Git 2.35 引入的改进版,会把两边相同的部分从冲突块里提出去,冲突区域更小:

git config --global merge.conflictStyle zdiff3

给 lock 文件配合并策略。 pnpm-lock.yaml 之类的文件冲突起来内容巨大且没法手工合,直接声明用哪边:

# .gitattributes
pnpm-lock.yaml merge=ours
package-lock.json merge=ours

配了之后还要注册一次这个 driver(Git 内置的 ours 驱动需要显式启用):

git config --global merge.ours.driver true

之后 lock 文件冲突会自动取当前分支的版本,合并完重跑一次 pnpm install 让它重新解析即可。

打开 rerere。 它会记录你解决过的冲突,遇到相同的冲突模式自动套用之前的解法。长期维护的分支反复 rebase 时能省掉大量重复劳动:

git config --global rerere.enabled true
💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEGit 合并后报错 Merge conflict marker encountered 解决加载中