先交代一个背景:我这几年代码评审里有个高频观察——几乎是每个团队,只要成员超过三个人,仓库里迟早会躺进来一两个数据库文件。
上午刚拉下来的新项目,一百多MB的 .db 文件在根目录躺着;某次紧急修复,同事“图省事”把 SQLite 文件直接提交进了仓库;还有更野的,把 MySQL 的物理 data 目录都凑进了 Git。这些操作当时看都挺“方便”,但代价都堆到了后面:仓库越拉越慢、分支合并总能撞上同一个文件冲突、更吓人的是某天发现生产环境的用户表数据都在 Git 历史里躺着,删都删不干净。
这篇就专门聊聊“数据库文件为什么不能提交”这件事。不扯空洞的理论,我会直接拆原因、给命令、讲场景,顺带把 Git 提交、版本历史清理、迁移脚本管理这些配套操作一起讲了。不管你是刚接触 Git 的新手,还是带团队的老兵,看完应该都能明白:数据库文件进仓库,伤的从来不是那一次提交,而是后面每一个 clone 仓库、拉分支、查历史的同事,以及若干年后后悔的自己。
1. 先分清你提交的到底是哪种“数据库文件”
很多人在这一步就踩坑了,因为“数据库文件”这四个字范围很广。如果连敌人都没看清,后面的处理方式全是瞎忙。我先按最常见的几种情况做个分类,你对照一下自己项目里躺着的到底是哪一类。
1.1 最常见的几种会被误提交的数据库文件
第一种是嵌入式数据库文件,最典型的就是 SQLite 的*.db、*.sqlite3、*.db3后缀文件。很多本地开发环境、测试工具、内部管理系统都在用 SQLite,因为它零配置、单文件、随拿随走。但也正因为“单文件”这个特性,它非常容易被git add .一把梭进去。
第二种是桌面软件或旧系统的数据文件,比如 Access 的*.mdb、*.accdb。这类文件在传统企业内网系统里依然大量存在,有开发经验的人应该都见过仓库里躺着.mdb的场景。它和 SQLite 一样,本质是“一个文件就是整个库”,而且文件格式对外部工具极不友好,一旦提交进 Git,diff 基本就是废的。
第三种是数据库的物理存储目录,比如 MySQL 的data目录、PostgreSQL 的base目录、SQL Server 的.mdf和.ldf文件。这个属于最重量级的误提交——把数据目录整个塞进 Git,轻则仓库体积爆炸,重则连带日志文件一起提交,里面全是敏感信息。
第四种是某些框架或中间件自动生成的持久化文件,比如 Java 项目里的 H2 数据库文件、Derby 的seg0目录,或者 Python 项目里缓存用的*.sqlite。这些文件通常出现在本地运行之后,开发人员下意识认为“反正程序能生成它,提交也没关系”,于是也跟着进了仓库。
1.2 数据库文件与普通代码文件的“性格差异”
我每次跟人解释为什么数据库文件不该提交,都喜欢打一个比方:代码文件是“菜谱”,数据库文件是“做好的菜”。菜谱可以反复传阅、修改、对比,但菜本身放久了会馊,而且不同的人做出来的味道完全不一样。
从版本控制的角度看,二者有几个本质区别。
第一,代码文件是文本,数据库文件基本是二进制(或者内部结构极其复杂的格式)。Git 对文本文件可以逐行做 diff、做合并,对二进制文件只能告诉你“它们不一样”,具体哪里不一样、怎么合并,Git 无能为力。
第二,代码文件的变更通常是“显式的、有意图的”,而数据库文件的变更是“隐式的、随机的”。你改一行代码,diff 里清清楚楚;但你往 SQLite 里插入一条记录,文件内部可能有多个页都发生了变化,从 Git 的角度看就是“整个文件都变了”。提交记录里根本看不出你这次提交的业务意图。
第三,代码文件理论上可以从源码重新生成,数据库文件是“运行结果”。一旦丢了,很难恢复;但如果你把它提交进 Git,又会带来一堆版本控制层面的新问题。这是一个“横竖都别扭”的处境。
所以先记住一个原则:版本控制里应该只放“菜谱”,不放“菜”。菜可以反复做,做菜的步骤和配方需要版本化。
2. 五个核心理由,说清“提交数据库文件”为什么是埋雷
2.1 无论你怎么提交,diff 都形同虚设
Git 的 diff 能力是文本级别的。拿 SQLite 文件来说,哪怕你只是往表里塞了一条记录,二进制的页面结构就可能变了一大片。Git 看到的结果大概率是:
diff --git a/app.db b/app.db index 3f2a1b5..8c94d62 100644 Binary files a/app.db and b/app.db differ看到这种输出,你就应该明白:版本控制的灵魂——追溯变化、定位问题、对比修改——在这里已经彻底失效了。
这意味着什么?假设某天你发现线上数据不对,想查一下两个月前某个版本的数据文件里到底是什么内容,你只能把这个历史版本拉下来,用数据库工具打开,肉眼对比。而这还只是“自己能查到”的理想情况,如果当时的文件格式和你现在的工具版本不兼容,连打开的资格都没有。
我见过不止一次这种场景:同事把 SQLite 数据库提交进仓库,过了三个月有人想回看历史数据,发现文件在那个 commit 里早就损坏了——因为提交的时候数据库进程还在写,文件本身就不一致。版本控制不但没帮上忙,反而给你保留了一份“坏掉的过去”。
2.2 合并冲突无解:两个人动了同一个文件,只能“你死我活”
Git 处理文本文件的合并时,会尝试两边的修改做三方合并。但对二进制数据库文件,Git 连尝试的资格都没有。一旦两个分支都改了同一个.db文件,合并时就必然产生冲突,而且这个冲突没法用正常的解决流程处理,只能二选一。
举个例子:你和同事分别在两个分支上开发,你在用户表里加了手机号字段,他给订单表加了支付状态字段。这两个改动从业务上看互不干扰,但如果你俩开发时都在本地运行着同一个 repo 里的 SQLite 文件,那么这个文件在两边都发生了变更。合并分支时,Git 直接提示冲突。你打开冲突文件,看到的是乱码,没有任何可编辑的内容。
最后你们只能做一个决定:保留谁的版本,然后重新加一遍对方的字段。更可怕的是,如果你们在本地已经录入了不少测试数据,这次合并很可能直接把其中一方的数据全部覆盖掉,再也找不回来。
这有点类似于并发扣库存那个经典问题:两个请求同时读到库存为 5,各自扣减 1,最后库存变成 4 而不是 3。问题根源就是没有合理的并发控制。而 Git 对数据库文件的合并处理也是同一个道理——它不做并发控制,它只会告诉你“这文件我管不了,你们自己看着办”。如果数据库文件本身还承载着本地测试数据,那最后的结果就只能是冲突加数据丢失,跟商品超卖一样不可收拾。
2.3 仓库体积膨胀:你提交的不是数据,是性能灾难
数据库文件和代码文件的增长模式完全不同。代码文件再大,一个文件撑死几十 KB;数据库文件随随便便就是几十 MB,数据多的时候上 GB 也很正常。
Git 仓库的历史是不可变的重型资产,任何被提交过的数据,即使后来删掉了,也会一直留在.git目录里。这意味着,哪怕你只在一个 commit 里误提交了一个 200MB 的数据库文件,后来发现不对又把它删了,你仓库的真实体积也已经永远增加了 200MB(压缩后小一些,但不会完全消失)。
仓库变大带来的直接后果有很多:
- 新同事 clone 代码的时间变长;
- 每次拉取、推送的传输数据量变大;
- 在 Git 客户端里浏览历史变得卡顿;
- CI 流水线每次拉代码的时间成本上升。
我见过一个最夸张的案例:某项目仓库体积膨胀到 5GB,其中 3.5GB 是历史中的 SQLite 文件和数据库备份文件,真正的源码只有 300MB。新同事入职第一天 clone 仓库,一等就是半小时,入职体验直接从“技术团队”降到“老式 ERP 系统”。后来我们花了一整天清理历史,才把仓库体积降回合理水平。
数据库文件的性能敏感性还体现在另一个细节上。SQL Server 这类数据库对底层存储 IO 是非常敏感的——你去看它的性能监控指标,平均读取等待时间如果到了 40 毫秒,DBA 已经开始紧张了。可你把它放到 Git 仓库里,同一份文件在不同成员的硬盘上表现差异巨大:有的人是 NVMe 固态,有的人是机械硬盘,有的人在虚拟机里,有的人在网盘同步目录里。指望所有人都能在这种环境下正常工作,本身就是不现实的。把数据库文件交给 Git 管理,等于把所有环境差异、性能问题全部引进了团队协作流程。
2.4 安全与合规风险:提交一次,永久裸奔
这条是分量最重的,也是我建议每个团队把它写进规范的原因。
数据库文件里存的是什么?用户信息、密码哈希、订单数据、身份证号、手机号、公司内部业务数据……只要运行过一段时间,里面基本什么都有。就算你用的是本地开发库,为了测试方便导入过一部分生产环境的脱敏数据,甚至只是几条真实用户记录,一旦提交进 Git,问题就来了。
Git 的特性决定了:被提交过的东西会存在于全量历史中。你在最新版本里删掉它没用,任何有能力访问仓库的人都可以通过历史提交把它翻出来。尤其是在 GitHub、GitLab 这类托管平台上,只要仓库可见性不是 Private,这些数据等于直接公布到了互联网上。就算你设置成 Private,团队之外也还有合作方、外包、离职员工等各种角色可能接触过仓库副本。
更麻烦的是,很多安全扫描工具(比如 GitLeaks、TruffleHog)会专门扫描 Git 历史来寻找敏感信息。如果你公司的安全团队跑一轮扫描,发现生产数据库文件躺在仓库历史里,这就不只是“技术失误”的问题了,而是安全事故。我知道有人会想“我提交的只是本地测试数据”,但问题是别人不知道你这些数据里有没有不可见的部分,等你意识到敏感信息混进去时,通常已经没法挽回——Git 历史里的数据几乎是无法被彻底清除的,除非改写全部历史并强制团队重新 clone。
2.5 环境耦合:本机能跑,换台机器就是坏的
数据库文件不是“纯数据”,它和操作系统、数据库版本、文件路径、编码设置都有很强的耦合关系。
我见过最常见的翻车场景是这样的:Windows 开发者用的是 SQLite,库文件路径在代码里写的是C:\project\data.db,提交进 Git 后,Linux 的同事拉下来,程序直接找不到文件,因为路径分隔符都不一样。稍微好一点的会写成相对路径,但依然可能因为 Windows 和 Linux 对文件名大小写的敏感度不同而翻车。
比路径更隐蔽的是数据库本身格式的兼容性问题。SQLite 的数据库文件格式虽然相对稳定,但不同版本的 SQLite 库可能会写入不同的内部结构;MySQL 的data目录跟版本强绑定,拿一个 5.7 的 data 目录放到 8.0 的实例里,大概率直接启动失败;SQL Server 的.mdf文件甚至不允许直接挂载到不同版本的实例上。
还有一些团队喜欢把“生产环境的数据”导出一份放进仓库,想着“大家共用一套数据,多方便”。结果就是开发环境数据和生产环境数据彻底混为一谈:开发人员一个误操作,可能会把生产数据改坏;测试数据里的脏数据又污染了本地开发;更别提生产环境真实数据被直接暴露给所有有权限访问仓库的人。环境耦合加上数据混淆,最后一定是混乱加倍。
3. 已经提交了?三步实操,把麻烦连根拔起
很多人的问题不是“要不要提交数据库文件”,而是“已经提交了,怎么亡羊补牢”。下面我按照从轻到重的处理流程,完整地走一遍。
3.1 立即止损:先把数据库文件从当前版本中移除
当前版本的处理其实很简单,关键是不要用错命令。我们的目标是从 Git 版本控制中移除这个文件,但保留本地文件继续使用。很多人会直接rm或手动删除,然后 commit,这样做会导致本地文件也没了,还得重新生成,很麻烦。
正确的命令是:
# 假设要移除的是根目录的 app.db git rm --cached app.db--cached参数的意思是“只从 Git 的索引(暂存区)中移除,不删除工作区文件”。执行完之后,app.db依然在你本地磁盘上,但 Git 已经不再跟踪它了。
接着把数据库文件写进.gitignore,防止以后再被加进来:
# .gitignore *.db *.sqlite *.sqlite3 data/最后提交这次变更:
git add .gitignore git commit -m "chore: 停止跟踪数据库文件,加入 .gitignore"如果你用的是 IDEA,操作过程会更直观:在 Project 面板里右键点击数据库文件,选择Git->Add to .gitignore,IDEA 会自动帮你生成规则;如果文件已经被跟踪,先执行上面的命令行移除,再点击忽略。IDEA 的 Commit 界面里也能取消勾选某个文件,但那只是“这次不提交”,并没有把它从 Git 跟踪列表里拿掉,治标不治本。
3.2 深挖历史:用 BFG 或 filter-repo 清理历史提交
如果你已经提交了好几轮,仓库历史里有多个数据库文件的快照,那仅靠上面的操作是不够的。历史里的那些文件必须清掉,否则仓库体积依然庞大,敏感数据依然存在。
这里推荐两个工具:git filter-repo(Git 官方推荐的新工具)和BFG Repo-Cleaner(老牌清理工具)。BFG 用起来最省事,适合“我要删掉所有历史中的某类文件”这种场景。
先安装 BFG(需要 Java 环境):
brew install bfg然后做一个裸仓库镜像:
git clone --mirror git@github.com:your-name/your-repo.git cd your-repo.git执行清理,删除所有历史提交里的*.db文件:
java -jar bfg.jar --delete-files '*.db' .最后强制更新远程仓库:
git push --force如果用git filter-repo,操作更直观一些:
pip install git-filter-repo git filter-repo --invert-paths --path-glob '*.db'需要特别提醒的是:历史改写的本质是重新生成所有 commit 的哈希值。也就是说,你和所有团队成员的本地仓库会和远程仓库“失联”,必须重新 clone 或者执行复杂的 rebase 才能对齐。所以这个操作一定要提前跟团队打招呼,约定一个时间点,让所有人先提交完自己的代码,然后统一执行清理、重新 clone。
3.3 同样重要:检查 dump 文件和备份文件是否也躺在仓库里
除了数据库系本身,还要检查那些“看起来不是数据库,但同样危险”的文件。
比如data.sql、backup.sql、dump.sql这类数据库导出文件。它们虽然不是二进制数据库文件,但内容里通常带着INSERT INTO的真实数据。如果这些数据里有敏感字段,风险等级和提交数据库文件完全一样,只是体积小一点。
我在仓库里甚至见过.zip的数据库备份压缩包,压缩文件比原始数据库文件更隐蔽,因为 GitHub 的代码浏览界面不会预览压缩包内容,但这不代表它不存在,更不代表 Git 历史里没有。
清理这类文件的思路和数据库文件一样:
# 在 .gitignore 中加入 *.sql *.dump *.bak *.zip注意,这里有个例外:如果你的.sql文件是“迁移脚本”(我下一节会讲),那它应该被提交,而且是非常应该被提交。所以不要一刀切地忽略所有.sql文件,要结合项目的目录规范来写.gitignore。比如规定好迁移脚本放在migrations/目录下,.gitignore里只忽略根目录和data/下的.sql文件,这样既安全又清晰。
# 只忽略根目录下的 sql 文件 /*.sql # 或忽略 data 目录 data/4. 数据库版本控制的正确姿势:脚本化、结构化、可回放
把数据库文件踢出 Git,不等于数据库就完全不需要版本管理了。恰恰相反,数据库需要版本管理,只是不能通过“提交数据库文件”这种方式。正确的做法是按“结构”和“数据”两类分开管。
4.1 结构变更用迁移脚本(Migration)
数据库的结构(表、字段、索引、约束、存储过程)和代码是高度耦合的。产品经理改了一个需求,后端代码要改,数据库的表结构很可能也要跟着改。如果数据库结构没有版本管理,就会出现代码库是最新的,但数据库结构还是旧的情况,程序一跑就报“字段不存在”的错误。
解决这个问题的成熟方案是迁移脚本(Migration)。主流 Web 框架和工具链几乎都内置了这套机制:
- Laravel 的
php artisan make:migration - Rails 的
rails generate migration - Python 生态的
Alembic(配合 SQLAlchemy) - Java 生态的
Flyway和Liquibase
迁移脚本的核心思路是:每次数据库结构变更,写一个带版本号的脚本,记录“从上一个版本变更到当前版本”需要执行哪些 SQL。比如:
-- 2024-06-01 增加用户手机号字段 ALTER TABLE users ADD COLUMN phone VARCHAR(20);这些脚本本身就是纯文本,完全适合放进 Git。代码在哪个版本,数据库结构就跟到哪个版本,团队任何人拉取代码后执行一遍迁移命令,本地数据库结构就是一致的。
对比提交数据库文件,迁移脚本的优势非常明显:文本可 diff,代码评审能看出结构变更的意图;演化有迹可循,每个字段的增删都能查到是什么 commit、什么原因;并且它是增量的,不会动不动就产生几百 MB 的体积变化。
4.2 初始数据用种子数据(Seed),业务数据不进 Git
数据库里还有一类数据需要版本化,比如系统运行必需的字典表、配置项、默认角色、默认菜单。这些属于“代码的一部分”,而不是“业务数据”。针对它们,业界同样有成熟方案:种子数据(Seed)。
以 Laravel 为例,你可以用 seeder 来版本化基础数据:
class UserRoleSeeder extends Seeder { public function run(): void { DB::table('roles')->insert([ ['name' => 'admin', 'description' => '管理员'], ['name' => 'editor', 'description' => '编辑'], ]); } }种子数据的特点在于:它和迁移脚本一样是文本、可版本化、可重复执行。新同事入职跑一次迁移和种子,数据库就有了基本可用的状态。
需要牢记的边界是:只把“必要的基础数据”和“演示用的假数据”交给种子脚本,永远不要通过这种方式提交真实的业务数据,更不能因为图省事把整个生产数据库 dump 下来再塞进种子脚本。种子数据一旦被提交,全团队就会以它为基准进行开发,里面混入的生产数据只会污染所有人的本地环境。
4.3 确实需要共享“数据”时,用 dump 文件,但要有前提
有些场景下,团队确实需要共享一份数据,比如复现线上 Bug 需要特定数据、接口联调需要一致的测试数据、数据分析需要一段历史数据集。这种时候,有人会习惯性地说“我把数据库文件发你一份”。如果通过 Git 来共享,建议用 dump 文件而不是原始数据库文件。
以 MySQL 为例:
mysqldump -u root -p your_database > data_dump.sql以 PostgreSQL 为例:
pg_dump -U postgres your_database > data_dump.sqlSQLite 也有对应的导出命令:
sqlite3 app.db .dump > dump.sql原因很简单:dump 文件是文本,能 diff、能按表导出、能选择性分享;而原始数据库文件是二进制,体积大、不可 diff、跨平台兼容性也差。
但这里有两个前提必须强调。
第一,提交 dump 文件前必须检查敏感数据。最好在导出时就做过滤或匿名化处理,把用户手机号、邮箱、地址等字段替换成测试数据。这个步骤宁可麻烦一点也不能跳过,因为 Git 历史一旦写入就真的很难抹掉。
第二,dump 文件不要频繁提交。它只适合在“明确需要共享某个阶段的数据”时使用,比如发版前的测试基准备份。如果每两天就提交一个新 dump,仓库还是会膨胀,历史还是会被塞满,你只是从一种坑掉进了另一种坑。
4.4 环境与数据的边界:容器化时代的新习惯
现在很多项目已经容器化了,数据库跑在 Docker 容器里,数据存在 volume 中。这个其实给了我们一个很清晰的边界:镜像和 Dockerfile 可以进 Git,volume 里的数据永远不进 Git。
如果你在本地开发用docker-compose.yml拉起一个 MySQL 或 PostgreSQL 实例,初始化脚本(init.sql或/docker-entrypoint-initdb.d/下的脚本)可以放仓库里,容器每次重建时会自动执行。而数据本身只存在本地的命名卷里,属于“运行态”的一部分,不属于仓库。
有个小技巧:如果你担心本地数据库数据丢了没法恢复,不要试图靠 Git 来兜底。正确做法是定期用数据库工具做“逻辑备份”或“物理备份”,存一份到你自己的备份目录或对象存储上。备份和版本控制是两件事,别混在一起。某些 ERP 系统(比如金蝶云星空)的数据库重建流程也应遵循同样的原则:重建库需要的是标准脚本和步骤文档,而不是从某个人的目录里拷一份原始文件来用。
5. 常见问题实录与避坑速查:这些坑我都替你踩过
5.1 Windows 下文件被锁定,Git 操作直接失败
这是一个非常典型的“提交失败”场景。SQLite 文件一旦被某个进程打开,Windows 下通常不允许其他进程覆盖或删除该文件。你在执行git pull或git checkout时,如果发现本地数据库文件正在被程序占用,操作就会直接失败,有时候甚至会导致文件损坏。
排查思路很直接:先关掉所有可能访问该数据库的程序(开发服务器、桌面客户端、数据库管理工具),再重新执行 Git 操作。如果还是提示占用,可以用lsof(macOS/Linux)或handle.exe(Windows)查一下是哪个进程占用了文件,先处理掉再继续。
更省事的做法是从根源避免:让程序把数据库文件放在 Git 工作区之外的位置,比如~/Development/data/或系统临时目录。这样即使程序一直在读写,也不会影响 Git 的操作。
5.2 成员 clone 下来后数据库打不开或版本不一致
这种情况常出现在“提交时数据库进程还没退出”或“文件拷贝时正好处于写入状态”的时候。你本地看着文件是好的,但提交到 Git 里的那一瞬间,文件可能处于不一致状态,别人拉下来自然打不开。
还有一种情况是数据库版本不一致。比如你本地用的是 SQLite 3.x 新版本,编辑过的数据库文件拿到一个只装 SQLite 2.x 客户端的同事机器上,根本识别不了。这个在“提交真实数据库文件”的协作方式下是无解的——你没法保证所有人用的数据库客户端版本完全一致。
根源解决方案依然只有一个:把数据库文件从协作路径里拿掉,改用迁移脚本加种子数据。如果临时需要一个团队共享的数据,统一用 dump 文件 + 文档说明,并指定大家用同一个版本的数据库客户端导入。
5.3 清理历史后,远程和本地仓库失联怎么处理
对已推送并多人使用的仓库执行历史清理(比如 BFG 或 filter-repo)后,所有人都需要重新同步。step-by-step 的操作是:
- 提前在群里通知,规定执行时间点,要求大家在此之前提交并推送完所有代码。
- 维护者执行清理并
git push --force更新远程。 - 团队成员删除本地旧的 clone,重新 clone 一次。
- 如果有本地未推送的分支,先备份,再在新 clone 上重新应用。
这一步确实麻烦,但没办法。因为历史清理改变了 commit 哈希,Git 不会认为旧仓库和新远程是同一个历史。与其让所有人手动处理一堆诡异的分叉,不如直接重新 clone,干净利落。
这里还要提一个常见误区:有人会在清理历史之后,又把数据库文件重新提交了一次。结果就是仓库体积重新开始膨胀。所以清理完历史下一步一定是把.gitignore规则检查清楚,双重保险,缺一不可。
5.4 .gitignore 常见误写与正确写法
最后做一份 .gitignore 的避坑速查。很多人不是不想忽略,而是写错了规则,导致规则没生效。
常见的错误写法:
# 错误示例 *.db这个规则本身没问题,问题是很多人把它写错了位置,或者写在了.git/info/exclude里。.git/info/exclude是本地私有规则,不会随仓库同步,对团队其他人没效果。要保证大家都生效,必须写到仓库里的.gitignore文件中。
第二个错误是规则太靠后。比如你写了:
*.db !data.sqlite如果data.sqlite已经被 Git 跟踪了,.gitignore里的规则不会让它“自动取消跟踪”。忽略规则只对“未跟踪文件”生效,已经被跟踪的文件需要先git rm --cached。
第三个常见问题是没考虑到目录结构。比如你的数据库文件在database/data.db,而你只写了:
*.db这其实已经能遮到所有子目录下的.db文件。但如果数据库文件是database/db.sqlite,那么*.db就匹配不到它了,因为后缀是.sqlite而不是.db。建议用更完整的规则:
*.db *.db3 *.sqlite *.sqlite3 *.mdb *.accdb最后提醒一句:.gitignore规则本身也要提交。有次我看到一位同事在本地配了一堆忽略规则,效果不错,但全都在.git/info/exclude里,其他人根本不知道有这个约定,第二天数据库文件又进仓库了。
最后说点个人经验
做了这么多年代码评审,我的经验总结起来其实就一条简单规则:仓库里只放“能重新生成的东西”,不放“运行结果”。数据库文件是运行产物,不是源代码,它需要的是备份、迁移、种子、脚本,而不是 commit。
我见过太多团队被这一个看似不起眼的习惯坑到:仓库越来越臃肿、合并越来越痛苦、历史里躺着用户数据、新人入职体验差到离谱。这些问题平时不爆发则已,一旦爆发就是好几个小时甚至好几天的折腾。与其到时候去清理几百 MB 的 Git 历史,不如从一开始就搞清楚哪些该提、哪些不该提。
如果今天这篇文章你只带走一个动作,那就是:回去检查一遍项目的.gitignore,看看仓库里有没有.db、.sqlite、.sql、.bak这类文件,有的话照着第三节的做法清一遍。做完之后,你会明显感觉到 Git 仓库和团队协作都清爽很多。