name: 增量提交 description: 将多文件更改分解为按依赖顺序排列的原子提交。用于重构、破坏性API变更或涉及3+文件的功能。
增量提交
当功能涉及多个文件时,以波次方式实现。每个波次是一个逻辑关注点,一个提交。这创建了一个干净的git历史,讲述一个故事。
模式
波次1:基础(类型、接口)
↓
波次2:工厂/构建器(创建实例的函数)
↓
波次3:合约/API(使用类型的公共接口)
↓
波次4:基础设施(实用工具、转换器、依赖)
↓
波次5:消费者(应用、UI、集成)
并非每个变更都需要所有波次。一个简单的bug修复可能是一个波次。一个横切重构可能需要五个。
波次特性
每个波次必须:
| 特性 | 描述 |
|---|---|
| 原子性 | 每个波次一个逻辑关注点 |
| 可构建性 | 代码在此波次后编译(运行类型检查) |
| 聚焦性 | 更改与一个层/关注点相关 |
| 完整性 | 波次内没有半途而废的工作 |
真实示例:模式重构
此功能将元数据从工作空间移动到表。五个波次:
波次1:类型
feat(schema): 添加IconDefinition、CoverDefinition和FieldMetadata类型
- 添加IconDefinition判别联合(emoji | external)
- 添加CoverDefinition判别联合(external)
- 在所有字段类型中添加带可选名称/描述的FieldMetadata
- 更新TableDefinition以使用icon/cover而不是emoji/order
文件:仅types.ts。一切的基础。
波次2:工厂
feat(schema): 在字段工厂函数中添加可选名称/描述
所有工厂函数(id、text、richtext、integer、real、boolean、date、select、tags、json)现在接受可选名称和描述参数。
文件:仅factories.ts。使用波次1的类型。
波次3:合约
feat(schema): 从WorkspaceSchema中移除emoji和description
Workspace现在只是一个包含guid、id、name、tables和kv的容器。
视觉元数据(icon、cover、description)现在位于TableDefinition上。
文件:仅contract.ts。使用新类型的API变更。
波次4:基础设施
feat(schema): 使用slugify生成人类可读的SQL列名
- 添加@sindresorhus/slugify依赖
- 添加使用slugify和'_'分隔符的toSqlIdentifier()辅助函数
- SQLite列现在使用field.name(或从key派生)而不是key
文件:to-drizzle.ts、package.json。使用字段元数据的实用工具。
波次5:消费者
feat(schema): 更新epicenter应用以使用TablesWithMetadata
- WorkspaceSchema现在接受TablesSchema | TablesWithMetadata
- 从包索引导出新类型
- 更新应用以创建带元数据的适当TableDefinition
文件:消费新类型的应用文件。
工作流
-
在编码前规划波次
- 列出需要更改的文件
- 按层/关注点分组
- 按依赖顺序排序(基础优先)
-
实现一个波次
- 仅对该波次进行更改
- 抵制“再修复一件事”的诱惑
-
验证波次
- 运行类型检查:
bun run tsc --noEmit - 确保没有引入错误
- 运行类型检查:
-
提交波次
- 使用常规提交格式
- 消息描述此波次完成的内容
- 正文可以列出具体更改
-
为下一个波次重复
何时使用波次
| 场景 | 使用波次? | 原因 |
|---|---|---|
| 单文件bug修复 | 否 | 一个变更,一个提交 |
| 添加新类型+工厂 | 可能 | 可能是1-2个波次 |
| 跨5+文件的重构 | 是 | 需要逻辑分组 |
| 破坏性API变更 | 是 | 类型 → API → 消费者 |
| 添加依赖并使用它 | 是 | 基础设施波次然后使用波次 |
反模式
巨型提交
refactor: 更新模式系统
- 添加新类型
- 更新工厂
- 更改合约
- 添加slugify
- 更新应用
问题:一个整体提交。无法二分查找,无法部分回滚,没有故事性。
微提交
feat: 添加IconDefinition类型
feat: 添加CoverDefinition类型
feat: 添加FieldMetadata类型
feat: 更新IdFieldSchema
feat: 更新TextFieldSchema
...
问题:过于细粒度。一个逻辑更改有20个提交。噪音。
错误顺序
波次1: 更新应用以使用新类型 ❌
波次2: 添加类型 ❌
问题:波次1无法编译。自底向上,而不是自顶向下。
依赖顺序启发法
决定波次顺序时,问:“这个文件导入什么?”
types.ts → 不导入任何内容(基础)
factories.ts → 导入types.ts
contract.ts → 导入types.ts
converters.ts → 导入types.ts,可能添加依赖
app/ → 导入以上所有内容
不导入任何内容的文件先来。导入所有内容的文件最后。
分支策略
对于多波次工作:
# 创建特性分支
git checkout -b feat/my-feature
# 波次1
# ... 进行更改 ...
git add <files> && git commit -m "feat(scope): 波次1描述"
# 波次2
# ... 进行更改 ...
git add <files> && git commit -m "feat(scope): 波次2描述"
# ... 继续波次 ...
# 完成后,所有波次都是分支上的独立提交
# PR显示特性如何演进的清晰历史
快速参考
开始前:
- [ ] 列出所有需要更改的文件
- [ ] 按层分组(类型、工厂、合约、基础设施、消费者)
- [ ] 按依赖顺序排序
对于每个波次:
- [ ] 仅更改此波次中的文件
- [ ] 运行类型检查
- [ ] 使用描述性消息提交
- [ ] 移动到下一个波次
所有波次后:
- [ ] 最终类型检查
- [ ] 如果适用,运行测试
- [ ] 创建具有干净提交历史的PR