问题背景
2026年起,AI越来越厉害,以前都是手写代码,后来有网页版早期的ChatGPT和Deepseek,开始资讯AI辅助写代码,最多就是复制AI给出的片段,贴到本地再适应本地项目。
而2026年后,完全可以让AI直接写Unity的或者其他引擎的代码了,甚至MCP可以直接帮你在Unity里编译好,完整debug后再交付给你,非常爽。
但是,我们团队开发一段时间后,发现了一个以前手写代码不会有的问题,那就是出现各种编码和行尾不一致的问题,一旦提交git或者svn,就会发现无缘故多了很多明明没改但提示变了的文件。这些误报会消耗很多时间精力去筛查,非常不值得。
调查情况
先看Unity这边,由于一般是用的VS来写的代码,的行尾和编码由编辑器决定的,默认行尾是CRLF,编码格式是UTF-8带BOM。但是AI编写的代码, 就由AI模型的习惯决定了,根据训练资料有关,大多LF和UTF8无BOM。
![]()
![]()
最大坑点是,有可能AI写好的代码,你用Visual Studio打开,不小心就会自动按照VS的默认设置改掉了编码和行尾,相反AI写的也可能会改掉原有文件的编码和行尾,来回变化,导致查看差异时很多误报。
到底有无BOM两者有什么区别
![]()
虽然都是UTF8,但是缺又不完全一样,到底什么区别,因此我也资讯了下AI答案:
BOM 是文件开头的三个字节,EF BB BF。它原本是给「不知道这是 UTF-8」的老工具打的一个标记。
C# 在 Windows 上的默认行为,是存成 UTF-8 带 BOM,有些配置下干脆是系统本地编码,中文 Windows 就是 GBK。
带 BOM 编译没问题,.NET 编译器认识它。麻烦出在别处。多出的三个字节会让某些 diff 工具把第一行算成改过。Linux 上的一些脚本、模板引擎、拼接工具会把 BOM 当成内容,输出里冒出一个看不见的字符。某些 JSON 解析器和字符串比较对 BOM 敏感。最烦的是抖动,同一个文件在「带 BOM」和「不带 BOM」的两台机器之间来回转,diff 会一直抖。
那到底统一成什么比较好呢?
Unity 的文档里提到,含非 ASCII 字符的脚本建议用带 BOM 的 UTF-8,保证各种环境都能正确识别编码。而 AI 生成的代码天然不带 BOM。一个主要靠 AI 写代码的项目,两边的默认值正好相反。
![]()
我其实一开始挺犹豫,让AI扫了我当前的项目编码情况,带和不带各站一半就很尴尬,特别是含中文注释的UTF8无BOM不知道有没有风险。
![]()
不过根据AI提供的资讯,带BOM主要用于老工具的识别,目前可能也用不到,无论带和不带,现在单独打开编辑器看中文注释都是没问题的。
项目往后主要靠AI写代码的话,还是应该顺着AI的默认值走,少一层来回转换的必要。所以我选择了B方案。
再看行尾的问题
AI 天然就是 LF,而Windows手写的默认是CRLF。不过我让AI实际扫描我当前项目,LF已成事实主流了。
![]()
因为同样的理由,未来会选择用AI写代码为主,所以顺应AI主流直接用LF作为规范统一是最合适的选择了。
![]()
约定成写入规范
一次性的整改只是临时的,要长期规范要真正写入成一个规范文件根治才行,AI检查后说我治理机制全缺,让我写这3个规范文件:
.editorconfig /.gitattributes /svn:eol-style
![]()
说实话以前手写代码的时候,我根本没见过这几个东西,从来没用过也能跑。但根据AI给出的资讯,其实他们在AI时代很重要,以下是AI给出的介绍:
.editorconfig卡在编辑器这一端。你新建或保存一个文件时,编辑器会先读它,按里面的规定决定用什么缩进、什么字符集、什么行尾。它是一份写给编辑器看的约定。
.gitattributes和svn:eol-style卡在版本控制这一端。文件入库的那一刻,它们负责把行尾和文本类型归一化。它们不关心你用什么工具写,只认提交这个动作。
最后一道是提交前检查,pre-commit 或者 CI。前两道都设好了,还是可能有人或者某个工具绕过去,那就靠它把漏网的文件拦下来。
最简单的文件.editorconfig
在项目根目录放一个.editorconfig,写这么几行就够了。
root = true
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
[*.{json,yml,yaml}]
indent_size = 2
[*.md] trim_trailing_whitespace = false
root = true表示这是最顶层,编辑器往上找到这里就停,不再往父目录找。
charset写utf-8,注意不是utf-8-bom。这一条就是「编码用 UTF-8、不带 BOM」的写法。
end_of_line写lf,对应「行尾用 LF」。
几种主流编辑器都原生读它,VS Code、Rider、Visual Studio 2022 都不用装插件。写一次,全项目生效,没有运行时成本。
放哪儿也简单。项目根目录最省事。如果只想管住 Unity 工程,放在Assets/下面也完全安全,Unity 会忽略点开头的文件,不会给它生成.meta,也不会当资源导入。
还有个容易忽略的点。VS 和 Rider 都是启动时读它,改完记得重开一次编辑器,不重开不生效。
它的弱点也很明确。它不强制。只在编辑器愿意遵守的时候才生效。你用命令行工具直接写文件、用脚本批量生成文件,或者让某个工具直接落盘,它一律管不着。
Git版本控制的.gitattributes
如果你用 Git,在仓库根目录放一个.gitattributes。
* text=auto eol=lf
*.cs text eol=lf
*.sln text eol=crlf
*.bat text eol=crlf
*.cmd text eol=crlf
*.png binary
*.dll binary
* text=auto eol=lf这一行是现在的推荐起手式。git 提交时把文本文件的行尾统一成 LF 存进仓库,检出到工作区也按 eol 走。仓库里永远只有一种行尾,跨平台协作不会互相覆盖。
下面几行是明确点名。.cs走 LF,.sln和 Windows 批处理按各自的要求单独给 CRLF,图片和 dll 直接标成 binary,别让 git 去猜。
SVN属性的svn:eol-style
SVN 没有 .gitattributes 那种放在仓库里、人人可见的声明文件。它的做法是给文件挂一个属性,属性名就叫svn:eol-style。
先分清楚几个值,选错了效果完全不一样。
native,仓库统一存 LF,检出时按平台转换。Windows 上检出成 CRLF,Linux 和 Mac 检出成 LF。适合「每个平台用自己习惯的行尾」的团队。
LF,仓库存 LF,检出也永远是 LF,在 Windows 上也是 LF。适合「行尾就统一成 LF,谁也别转」的团队。
我要的是后一种,所以选 LF。方向定了就是全项目 LF,不要再让平台转换插一脚。
怎么设。四种方式,从最直接的到最省事的。
方式一,图形界面。在要处理的文件夹上右键,TortoiseSVN,Properties,New,eol-style,选 LF,勾上「Set property recursively」,确定。适合只改一两个目录。
方式二,命令行按目录递归。一行就够。
svn propset svn:eol-style LF --recursive Assets
svn propset svn:eol-style LF --recursive .
这里有个容易误解的点。SVN 的属性不继承。--recursive是把每个文件挨个设一遍,不是「给文件夹设了,子文件就自动有」。文件夹上挂属性,不会传给里面的文件。
方式三,命令行按清单批量。如果只想处理某类文件,还要避开第三方目录,先生成一份清单再喂给它。
svn list -R > all-files.txt
svn propset svn:eol-style LF --targets cs-list.txt
第一行把受版本控制的文件全列出来。编辑这份清单,只留 .cs,把Plugins/下面的删掉,存成 cs-list.txt。--targets后面就是一个一行一个路径的文本文件。我就是这么只给版本化的 .cs 设的属性,第三方插件一个没动,别人的代码别去改。
方式四,auto-props,让新文件自动带上。前面三种只管现有文件,之后新加的文件属性还是空的。在本机的 Subversion config 里加两段,新文件一svn add就自动带上。
[miscellany]
enable-auto-props = yes
[auto-props]
*.cs = svn:eol-style=LF
*.csproj = svn:eol-style=LF
*.bat = svn:eol-style=CRLF
*.cmd = svn:eol-style=CRLF
config 文件在 Windows 上一般在%APPDATA%\Subversion\config。改完对之后新加的文件生效。
这里有两个坑。一是enable-auto-props = yes不打开,下面的 auto-props 根本不生效,这个最常见。二是 auto-props 只对之后新增的文件起作用,已经入库的老文件得靠前面三种方式手动补。
设完别忘了提交。propset 只改了你的工作副本,属性本身也是受版本控制的。要让团队其他人都拿到,得svn commit一次。
怎么确认真的设上了。查单个文件,用 propget 加属性名和文件路径。想给整个目录体检,加--recursive,它会逐个列出路径和属性值,没设的显示 none。
svn propget svn:eol-style Assets/Scripts/Player.cs
svn propget svn:eol-style --recursive Assets
顺手再看一眼svn status。属性变更和内容变更在输出里占的是两列,别搞混。
MM Assets/Scripts/Player.cs
M Assets/Scripts/Player.cs
第一列是内容状态,第二列是属性状态。MM 是内容和属性都变了。前面空一格、只有一个 M 的,是只改了属性。我第一次看的时候就没分清这两列。
设错了想撤销,把 propset 换成 propdel,一行删掉。
svn propdel svn:eol-style --recursive Assets
改成 LF 之后,SVN 还能 diff 出差异吗
这是我最关心的问题。我先拿一个文件做了次受控实验,结论分两层。
![]()
第一层,只改行尾,SVN 会不会认为文件变了。答案取决于有没有设 svn:eol-style。
没设的时候,SVN 是按行比较的,行尾符号也算在这一行里。你把整份文件的 CRLF 换成 LF,在它眼里就是每一行都变了,diff 变成整份删除加整份重新添加。几百行假差异,真正改的那几行得在噪声里捞。
设了之后,SVN 比较的是归一化之后的内容。纯行尾的变化被它吃掉了,不再产生 diff。只有你真正改过的行才会出现。
所以加 svn:eol-style 最大的好处,不是「能 diff」,而是让 diff 干净。它把行尾这一类噪声永久性地从 diff 里删掉了。
第二层,那设置属性本身,会不会造成一次全文件改动。可能会,而且这是必须付的一次性代价。
如果你的文件在仓库里存的是 CRLF,属性却要求 LF,那这次提交会让整个文件看起来被改了。这是一次性代价,吃掉它就好。
正确的做法是三步。第一步,把要改的文件挑出来,设属性、顺手把内容归一化。第二步,就这一次,单独提交,提交信息写明「纯格式变更,无逻辑改动」。第三步,之后正常写代码,diff 里只剩你真正动过的那几行。
关键在第二步。格式变更必须单独一条提交,不能和功能改动混在一起。混在一起,你以后想单独回滚这次格式变更都做不到。
总结经验
在AI时代里,编码和行尾的变化问题是值得重视的,与手写时代的默认值不同了。
非常建议加上.editorconfig /.gitattributes /svn:eol-style这3个设置,至于怎么写怎么加,找AI搞就行,只不过你不说,AI就不会去做。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.