【踩坑记录】UTF-8 和 GBK 编码冲突导致代码全变?Git 为什么没有提示冲突?
前言最近在做 Memoria 项目的 Flutter / Dart 代码时遇到了一个非常经典但又很隐蔽的问题一个原本正常的 UTF-8 文件被某个环境用 GBK 打开并保存后文件内容看起来变成了大量乱码、转义字符代码 diff 也变得非常恐怖。更坑的是 Git 并不会认为这是“编码冲突”它只会认为这是一次普通的文件修改。也就是说如果一个人把 UTF-8 文件用 GBK 保存了然后再提交另一个人 pull 下来之后Git 很可能不会爆冲突而是直接把“变了编码后的文件”合并下来。这篇文章记录一下这个问题的原因、排查方式、修复方式和后续项目规范。一、问题现象原本 Dart 文件里有一些中文 prompt、中文注释、语义搜索规则等内容。结果某次打开后文件里出现了大量类似下面的内容\u8bf7\u6839\u636e\u7528\u6237\u8f93\u5165...或者直接出现乱码璇锋牴鎹甤...在 IDE 里看起来就是中文变成乱码字符串被转义成一堆\uXXXXGit diff 显示整个文件大面积变化代码本身逻辑没怎么改但几百行甚至上千行都变了。这种情况在包含中文 prompt、中文配置、中文注释的项目里尤其容易出现。二、根本原因Git 只比较字节流不理解文本编码这个问题的核心原因是Git 本质上管理的是文件内容的字节流而不是“人眼看到的文本”。同一个中文字符在 UTF-8 和 GBK 里的字节表示是不一样的。例如中文字符中在 UTF-8 中是E4 B8 AD在 GBK 中可能是D6 D0人看到的都是“中”但底层字节完全不同。所以当一个 UTF-8 文件被 GBK 打开、编辑、保存后文件的底层字节流可能会发生大面积变化。Git 看到的是旧字节流 ! 新字节流于是它会认为这个文件被正常修改了而不是这个文件发生了编码冲突三、为什么 Git 不会自动报冲突很多人会以为既然文件被弄乱了Git 应该提示 conflict 吧但实际上Git 的冲突检测主要发生在三方合并时base 版本 你的版本 对方版本只有当两边修改了同一片内容并且 Git 无法自动判断该怎么合并时才会出现 HEAD ... ... branch但是编码变化通常有两种情况情况 1只有一个人改了编码比如A 提交正常 UTF-8 文件 B 提交把文件用 GBK 保存了 C pull直接拿到 B 的结果这时候 Git 认为 B 只是正常修改文件不会产生冲突。情况 2两个人改了不同区域比如A 改了上半部分代码 B 用 GBK 保存导致全文件变化Git 可能仍然会尝试自动合并不一定冲突。情况 3两个人改了同一行这种情况下有可能冲突但冲突原因仍然是“同一行被修改”不是“UTF-8 和 GBK 冲突”。所以结论是Git 没有“编码冲突”这个概念。它只知道字节变了不知道你是 UTF-8、GBK、Shift-JIS 还是别的编码。四、这个问题为什么很危险这个坑危险在于它不像语法错误那样立刻爆炸。它可能表现为代码还能编译逻辑似乎没变Git 没有冲突提示review 的时候 diff 太大别人不一定仔细看最后乱码文件被正常 merge 进主分支。尤其是有中文 prompt 的 AI 项目这个问题会更严重。例如final prompt 你是一个语义查询解析器请把用户输入解析成 JSON。 ;如果 prompt 编码坏了模型收到的提示词可能就变成乱码后续效果会非常诡异搜索结果不稳定LLM 输出格式异常中文查询解析失败原本能识别“上个月”“济南”“照片”的逻辑突然失效。这类 bug 很难第一时间想到是编码问题。五、如何判断是不是编码问题1. 看 Git diff如果你发现某个文件明明只改了一点点但 diff 显示几百行都变了就要警惕git diff path/to/file.dart典型现象- 正常中文 \u6b63\u5e38\u4e2d\u6587或者- 请根据用户输入解析 璇锋牴鎹甤...2. 看 IDE 右下角编码在 VS Code、Android Studio、JetBrains 系 IDE 里一般右下角会显示当前文件编码UTF-8 GBK GB2312 UTF-8 with BOM如果 Dart / Markdown / JSON 文件不是 UTF-8就要注意。3. 用命令检查编码Linux / macOS 可以用file -bi path/to/file.dart可能输出text/plain; charsetutf-8或者text/plain; charsetiso-8859-1Windows 下也可以用 Python 简单检测from pathlib import Path path Path(lib/service/semantic_query_parser_lm.dart) data path.read_bytes() try: data.decode(utf-8) print(UTF-8 OK) except UnicodeDecodeError as e: print(Not valid UTF-8:, e)六、如何修复已经被 GBK 保存的文件情况 1你不需要保留当前修改这是最简单的情况直接恢复git restore path/to/file.dart或者恢复整个工作区git restore .注意这会丢弃本地未提交修改。情况 2你需要保留代码逻辑修改如果里面确实有一些有效代码改动但是编码坏了可以这样处理先备份当前文件从 Git 恢复一份正常 UTF-8 版本手动把有效代码逻辑迁移过去不要直接整文件覆盖最后确认 diff 只包含真正的逻辑修改。建议流程copy lib/service/semantic_query_parser_lm.dart lib/service/semantic_query_parser_lm.dart.bak git restore lib/service/semantic_query_parser_lm.dart然后打开.bak只复制真正需要的代码片段。情况 3只是编码错了内容还可逆如果确定文件是 GBK 编码可以尝试转回 UTF-8iconv -f gbk -t utf-8 old.dart new.dart或者 Pythonfrom pathlib import Path src Path(old.dart) dst Path(new.dart) text src.read_text(encodinggbk) dst.write_text(text, encodingutf-8)但注意如果文件已经经历过多次错误打开和保存不一定能完全恢复。七、如何防止再次发生1. 统一项目编码全部使用 UTF-8在项目根目录加.editorconfigroot true [*] charset utf-8 end_of_line lf insert_final_newline true trim_trailing_whitespace true [*.dart] charset utf-8 end_of_line lf [*.md] charset utf-8 end_of_line lf [*.json] charset utf-8 end_of_line lf这样支持 EditorConfig 的 IDE 会自动使用 UTF-8。2. VS Code 设置项目里可以加{ files.encoding: utf8, files.autoGuessEncoding: false, files.eol: \n }建议关闭自动猜编码因为自动猜有时反而会把 UTF-8 文件识别成 GBK。3. JetBrains / Android Studio 设置路径一般是Settings / Preferences - Editor - File Encodings建议设置Global Encoding: UTF-8 Project Encoding: UTF-8 Default encoding for properties files: UTF-8如果 IDE 提示Reload in another encoding Convert to UTF-8要优先选择Convert to UTF-8而不是随便用 GBK 重新保存。4. 添加 Git 提交前检查可以写一个简单脚本阻止非 UTF-8 文件提交。例如scripts/check_utf8.pyfrom pathlib import Path import sys TEXT_EXTS { .dart, .md, .json, .yaml, .yml, .txt, .py, .js, .ts, .html, .css } failed [] for path in Path(.).rglob(*): if not path.is_file(): continue if .git in path.parts: continue if path.suffix.lower() not in TEXT_EXTS: continue try: path.read_text(encodingutf-8) except UnicodeDecodeError: failed.append(str(path)) if failed: print(以下文件不是合法 UTF-8) for f in failed: print( -, f) sys.exit(1) print(UTF-8 check passed.)运行python scripts/check_utf8.py也可以接到 pre-commit 里。5..gitattributes统一换行.gitattributes不一定能完全解决编码问题但可以规范文本文件和换行* textauto *.dart text eollf *.md text eollf *.json text eollf *.yaml text eollf *.yml text eollf *.py text eollf注意.gitattributes主要解决文本归一化和换行问题不能指望它自动帮你识别所有编码错误。八、团队协作时怎么说清楚这个问题这类问题最好不要只说你代码乱码了。而是应该明确说明这个文件原本是 UTF-8但被 GBK 或其他编码方式打开并保存了。 Git 只比较字节流不理解文本编码所以它会把这当成普通修改不一定产生冲突。 建议恢复原文件后只迁移真正的逻辑修改并统一 IDE 编码为 UTF-8。这样对方更容易理解问题本质。九、这次给我的教训这次问题给我的最大提醒是对包含中文 prompt、中文注释、中文配置的 AI 项目来说编码规范不是小事。尤其是像 Memoria 这种项目里面会有很多中文语义解析 prompt例如时间解析地点解析相册事件生成语义检索LLM 输出 JSON 模板中文标签和规则。这些内容一旦被错误编码污染表面看起来可能只是“中文显示不正常”但实际上可能影响整个模型调用链路。所以后续我会把 UTF-8 编码检查作为项目工程规范的一部分。十、总结这次问题可以总结成一句话Git 只管理字节流不理解文本编码。UTF-8 文件被 GBK 打开并保存后底层字节完全变化Git 只会认为这是普通修改不会自动触发所谓“编码冲突”。解决方案项目统一使用 UTF-8IDE 全局设置 UTF-8不要用 GBK 保存代码文件发现 diff 大面积异常时优先怀疑编码问题对已经污染的文件优先从 Git 恢复再手动迁移有效逻辑添加.editorconfig、.gitattributes和 UTF-8 检查脚本团队协作时明确说明这是编码问题不是普通代码风格问题。这类 bug 不难修但如果不重视很容易悄悄污染主分支。所以尤其是中文 AI 项目一定要记住代码文件统一 UTF-8。 Prompt 文件统一 UTF-8。 配置文件统一 UTF-8。 不要让 GBK 混进来。