为什么你不再需要git add:lint-staged v10+自动暂存变更与避免竞态条件的原理
【免费下载链接】lint-staged🚫💩 — Run tasks like formatters and linters against staged git files项目地址: https://gitcode.com/gh_mirrors/li/lint-staged
lint-staged 是一款在 Git 提交前对暂存区(staged)文件运行格式化器、linter 等任务的命令行工具,核心口号是"别让屎进你的代码库"。从v10版本开始,lint-staged 内置了自动暂存(auto-staging)能力:任务修改文件后,它会统一执行git add把改动写回 Git 索引,同时避免多个任务并发编辑同一文件时产生的竞态条件(race condition)。你只需要配置prettier --write这类命令,再手动追加git add的做法已经成为历史。
🤔 旧式写法为什么危险:手动 git add 的竞态隐患
在 v10 之前,典型的 lint-staged 配置长这样:
{ "*.{js,ts}": ["prettier --write", "eslint --fix", "git add"] }每个任务链的最后都要手动加一步git add。这看似无害,却在并行执行时埋下两大隐患:
- 并发写冲突:lint-staged 默认并行运行任务(见
--concurrent选项)。当两个任务都匹配到同一个文件时,任务 A 可能还在写文件,任务 B 就抢先执行了git add,把写到一半的中间状态暂存进了索引。 - 索引被半提交污染:某个任务失败退出时,另一个任务的
git add可能已经执行,导致索引和工作区不一致,排查起来非常痛苦。
官方在 v10.0.0 的发布说明中明确写道:这一行为被整合进 lint-staged 本体,"就是为了防止多个任务编辑同一文件时的竞态条件",并建议移除配置中的git add。详见 CHANGELOG.md 中 v10.0.0 的 BREAKING CHANGES 章节,迁移说明也可参考 MIGRATION.md。
⚙️ 自动暂存核心:updateIndex 是如何工作的
lint-staged v10+ 的整个 Git 生命周期由 lib/gitWorkflow.js 中的GitWorkflow类驱动,其中负责"自动git add"的就是updateIndex方法(lib/gitWorkflow.js),它有几个关键设计:
- 只加"匹配过的文件":方法内部维护了一个
matchedFiles集合(在 lib/runAll.js 中收集),即只把被任务 glob 匹配、且确实被任务修改过的暂存文件加回索引,不会误伤其他文件。 - 串行执行
git add:代码注释写着 "Needs to be run serially because of locking Git operation"——因为 Git 写索引时会加.lock锁,所以git add必须逐个串行执行,天然杜绝了并行写索引的竞争。 - 处理长命令行:通过 lib/getSpawnedTasks.js 中的
chunkFilesForCommand把文件列表按平台命令行长度上限(macOS 262144 / Windows 8191 / Linux 131072 字符)二分切块,避免参数过长。 - 感知特殊索引场景:如果检测到
GIT_INDEX_FILE指向的是非默认 lock 文件(例如git commit pathspec的用法),它会额外再更新一次默认索引,防止提交后留下残留 diff。 - 空提交保护:
git add之后重新读取暂存文件列表,如果任务把所有改动都"格式化没了"导致提交为空,会抛出ApplyEmptyCommitError阻止空提交——除非你显式使用了--allow-empty选项。
一句话总结:所有任务跑完之后,由 lint-staged 在唯一的、串行的时间点统一执行一次git add,竞态条件自然消失。
🔄 完整工作流:自动暂存发生在哪一步
lib/runAll.js 串起了整个流程,updateIndex恰好卡在"任务执行之后、恢复现场之前":
| 步骤 | 方法 | 作用 |
|---|---|---|
| ① 备份 | prepare() | 创建备份 stash,保护原始状态 |
| ② 隐藏未暂存改动 | hidePartiallyStagedChanges() | 让任务只看到"本次要提交的内容" |
| ③ 并行跑任务 | runTasks() | 执行 prettier / eslint 等 |
| ④自动暂存 | updateIndex() | 统一git add任务产生的修改 |
| ⑤ 恢复现场 | restoreUnstagedChanges() | 把第 ② 步隐藏的改动用 patch 贴回去 |
| ⑥ 清理 | cleanup() | 删除备份 stash |
如果中间任何一步出错,restoreOriginalState()会把工作区回滚到提交前的状态,备份 stash 还能通过git stash list找回,防止数据丢失。
🧹 如何迁移:把 git add 从配置里删掉
升级过程非常简单——打开你的配置(比如项目根目录的 lint-staged.config.js),把任务链里的git add删掉即可:
{ "*.{js,ts,md}": "prettier --write" }如果忘了删也没关系:lint-staged 会扫描每个任务的命令字符串,一旦检测到包含git add,就会在控制台打印黄色警告(逻辑见 lib/runAll.js,文案来自 lib/messages.js):
⚠ Some of your tasks use
git addcommand. Please remove it from the config since all modifications made by tasks will be automatically added to the git commit index.
🛠️ 相关选项速查:控制自动暂存行为
根据 README.md 的命令行参数文档,有几个选项直接影响自动暂存的行为:
--fail-on-changes:任务改动了文件时不自动暂存,而是直接以退出码 1 失败。适合"格式化必须干净通过"的严格团队。--allow-empty:任务把所有暂存改动都还原(比如格式化回退了改动)时,允许继续创建空提交。--no-revert:出错时不回滚原始状态,方便你手动接管。--no-stash:禁用备份 stash 机制,适合 CI 等确定性环境。
对应的自动化测试可参考 test/integration/allow-empty.test.js、test/integration/fail-on-changes.test.js 和 test/integration/no-revert.test.js。
✅ 总结
lint-staged v10+ 把git add从"每个任务的末尾步骤"提升为"工作流中一次受控的串行操作":
- 省心:配置里只写工具命令,暂存交给工具链托管;
- 安全:串行
git add+ 索引锁感知,彻底消除多任务并发写索引的竞态条件; - 可靠:备份 stash + 自动回滚 + 空提交保护,出错也不怕丢改动。
下次写 pre-commit 钩子时,就可以安心把git add从脑子里删掉了——lint-staged 已经替你把它做对了。
【免费下载链接】lint-staged🚫💩 — Run tasks like formatters and linters against staged git files项目地址: https://gitcode.com/gh_mirrors/li/lint-staged
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考