Git代码提交与PR规范指南Skill git

本技能提供Git提交和拉取请求的规范指南,包括约定式提交格式、最佳实践、PR描述模板和视觉沟通技巧,帮助团队提高代码协作效率、版本管理质量和项目可维护性。关键词:Git, 提交规范, 拉取请求, 代码协作, 版本控制, DevOps, 软件开发

DevOps 0 次安装 0 次浏览 更新于 3/20/2026

名称: git 描述: 使用约定式提交的Git提交和拉取请求指南。适用于创建提交、编写提交消息、创建PR或审查PR描述时。

Git提交与拉取请求指南

约定式提交格式

<类型>[可选范围]: <描述>

[可选正文]

[可选页脚]

提交类型

  • feat: 新功能(与语义版本中的MINOR对应)
  • fix: 错误修复(与语义版本中的PATCH对应)
  • docs: 仅文档更改
  • refactor: 既不修复错误也不添加功能的代码更改
  • perf: 性能改进
  • test: 添加或修改测试
  • chore: 维护任务、依赖更新等
  • style: 代码风格更改(格式化、缺少分号等)
  • build: 构建系统或依赖项更改
  • ci: CI配置文件和脚本更改

范围指南

  • 范围是可选的:仅在提供清晰度时添加
  • 使用小写,放在类型后的括号中:feat(转录):
  • 优先使用特定组件/模块名称,而非通用术语
  • 您当前的做法很好:组件名称(EditRecordingDialog)、功能区域(transcriptionsound
  • 避免过于通用的范围,如uibackend,除非确实合适

何时使用范围

  • 当更改局限于特定组件/模块时
  • 当有助于区分类似更改时
  • 当在具有不同区域的大型代码库中工作时

何时不使用范围

  • 当更改同等影响多个区域时
  • 当类型本身足够描述性时
  • 对于小而明显的更改

描述规则

  • 冒号和空格后立即以小写开头
  • 使用祈使语气(“添加”而非“已添加”或“添加”)
  • 结尾不加句号
  • 第一行保持在50-72个字符以内

破坏性更改

  • 在类型/范围后添加!,冒号前:feat(api)!: 更改端点结构
  • 在页脚中包含BREAKING CHANGE:及详细信息
  • 这些触发语义版本中的MAJOR版本升级

遵循您风格的示例:

  • feat(transcription): 为OpenAI提供者添加模型选择
  • fix(sound): 解决资产模块中的音频导入路径
  • refactor(EditRecordingDialog): 实现工作副本模式
  • docs(README): 澄清成本比较部分
  • chore: 更新依赖到最新版本
  • fix!: 更改默认转录API端点

提交消息最佳实践

“为什么”比“什么”更重要

提交消息主题行描述了什么更改。提交正文解释了为什么。

好的提交(解释动机):

fix(auth): 防止文件上传期间会话超时

用户在上传大文件时会在中途被登出,因为会话刷新仅在导航时触发,而不是后台活动时。

差的提交(仅描述什么):

fix(auth): 添加上传处理程序的保活调用

第一个提交告诉未来开发者为什么代码存在。第二个让他们深入代码以理解目的。

其他最佳实践

  • 永远不要包含Claude Code或opencode水印或归属
  • 每个提交应代表一个单一、原子性的更改
  • 为未来开发者(包括您自己)编写提交
  • 如果需要多行描述您做了什么,考虑拆分提交

拉取请求指南

动机优先

每个PR描述必须从为什么存在此更改开始。不是更改了什么文件,不是如何工作——为什么。

好的PR开头

用户在上传大文件时会在中途被登出。会话刷新仅在导航时触发,而不是在上传等后台活动期间。

差的PR开头

此PR添加上传处理程序的保活调用并更新会话刷新逻辑。

读者应在看到解决方案前理解问题。

API更改必须包含代码示例

如果PR引入或修改API,您必须包含显示如何使用的代码示例。没有例外。

需要代码示例的情况

  • 新函数、类型或导出
  • 函数签名更改
  • 新CLI命令或标志
  • 新HTTP端点
  • 配置更改

好的API PR(显示实际使用):

// 定义操作一次
const actions = {
	posts: {
		create: defineMutation({
			input: type({ title: 'string' }),
			handler: ({ title }) => client.tables.posts.create({ title }),
		}),
	},
};

// 传递给适配器 - 它们生成CLI命令和HTTP路由
const cli = createCLI(client, { actions });
const server = createServer(client, { actions });

差的API PR(仅描述而不显示):

此PR添加一个从操作定义生成CLI命令和HTTP路由的操作系统。

第一个版本让审阅者一目了然地理解API。第二个迫使他们深入代码以理解调用点。

重构时使用前后代码片段

代码示例不仅适用于API更改。对于不改变公共API但改变代码结构的内部重构,前后代码片段具体展示改进:

// 之前:直接使用YKeyValueLww并手动扫描
const ykv = new YKeyValueLww<unknown>(yarray);

function reconstructRow(rowId) {           // O(n) - 扫描每个单元格
  for (const [key, entry] of ykv.map) {
    if (key.startsWith(prefix)) { ... }
  }
}

// 之后:组合的存储层
const cellStore = createCellStore<unknown>(ydoc, TableKey(tableId));
const rowStore = createRowStore(cellStore);

rowStore.has(id)           // O(1)
rowStore.get(id)           // O(m) 其中 m = 每行字段数
rowStore.count()           // O(1)

在以下情况使用前后片段:

  • 内部实现发生重大变化,即使外部API未变
  • 性能特征改变,代码显示原因
  • 复杂度被移动/分解(显示内联内容与现在委托的内容)

使用ASCII艺术进行视觉沟通

广泛使用ASCII图表沟通复杂想法。它们比散文更易扫描,并一目了然地显示关系。

旅程/演进图

对于迭代先前工作的PR,显示演进:

┌─────────────────────────────────────────────────────────────────────────┐
│  PR #1217 (1月7日)                                                       │
│  "添加YKeyValue以实现1935倍存储改进"                                    │
│                                                                         │
│       Y.Map (524,985字节) ──→ YKeyValue (271字节)                       │
│                                                                         │
└───────────────────────────────────┬─────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│  PR #1226 (1月8日)                                                       │
│  "移除YKeyValue,使用原生Y.Map + 时代压缩"                               │
│                                                                         │
│  理由:"不可预测的LWW行为"  ← ⚠️ (误导性!)                              │
│                                                                         │
└───────────────────────────────────┬─────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│  此PR                                                                   │
│  "恢复带LWW时间戳的YKeyValue"                                          │
│                                                                         │
│  原因:基于时间戳的解析给出直观的"最新胜出"                            │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

分层架构图

显示组件如何堆叠:

┌─────────────────────────────────────────────────────────────┐
│  defineWorkspace() + workspace.create()                     │  ← 高级
│    内部创建Y.Doc,绑定表/kv/能力                            │
├─────────────────────────────────────────────────────────────┤
│  createTables(ydoc, {...}) / createKv(ydoc, {...})          │  ← 中级
│    绑定到现有Y.Doc                                          │
├─────────────────────────────────────────────────────────────┤
│  defineTable() / defineKv()                                 │  ← 低级
│    纯模式定义                                              │
└─────────────────────────────────────────────────────────────┘

比较表

显示方法之间的权衡:

┌────────────────────────────────────────────────────────────────┐
│  使用场景                         │  推荐方法                    │
├───────────────────────────────────┼────────────────────────────┤
│  实时协作,简单情况               │  YKeyValue (位置性)          │
│  离线优先,多设备                 │  YKeyValueLww (时间戳)      │
│  时钟同步不可靠                   │  YKeyValue (不依赖时钟)      │
└────────────────────────────────────────────────────────────────┘

流程图

显示数据/控制流:

┌────────────────────────────────────────────────────────────────┐
│                     冲突解析                                   │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  客户端A (下午2:00) ──┐                                        │
│                       │──→  同步  ──→  胜者?                 │
│  客户端B (下午3:00) ──┘                                        │
│                                    │                           │
│                   ┌────────────────┴────────────────┐          │
│                   ▼                                 ▼          │
│             YKeyValue                         YKeyValueLww     │
│          (客户端ID胜出)                     (时间戳胜出)       │
│           ~50% 正确率                       100% 正确率        │
│                                                                │
└────────────────────────────────────────────────────────────────┘

组合树图

对于改变模块组合方式的重构,使用轻量级缩进树符号而不是重箱线图。这能一目了然地显示依赖/组合层次:

之前 — 一个模块做所有事情:

TableHelper (模式 + CRUD + 行重建 + 观察者)
  └── YKeyValueLww  ←  Map<"rowId:colId", 条目>
        ├── reconstructRow()   O(n) 扫描所有键寻找前缀
        ├── collectRows()      O(n) 按rowId分组所有单元格
        └── deleteRowCells()   O(n) 过滤 + 删除

之后 — 每个层有单一职责:

TableHelper (模式验证、类型化CRUD、品牌化ID类型)
  └── RowStore (内存行索引 → O(1) has/count, O(m) get/delete)
      └── CellStore (单元格语义:键解析、类型化更改事件)
          └── YKeyValueLww (通用LWW冲突解析原语)

组合树的关键属性:

  • 对于单子节点使用└──,存在兄弟节点时使用├──
  • 用括号注释每个节点的职责
  • 当重构改变性能特征时显示
  • 前后对比使改进立即可见

文件重定位树

当重构物理移动文件且重定位本身就是架构声明时,将移动模式显示为树。这不是“列出更改的文件”(技能禁止)——它显示结构重组:

packages/epicenter/src/
├── shared/
│   ├── y-cell-store.ts      →  dynamic/tables/y-cell-store.ts
│   └── y-row-store.ts       →  dynamic/tables/y-row-store.ts
└── dynamic/tables/
    └── table-helper.ts         (重构以组合上述内容)

在以下情况使用文件重定位树:

  • 作为模块边界更改的一部分,文件在目录之间移动
  • 新位置传达架构意图(例如,“这些属于表子系统,不是共享的”)
  • 移动了2-6个文件;更多时,描述模式

不要在以下情况使用:

  • 文件重命名但停留在同一目录
  • 移动是真实更改的附带事件

何时使用图表

  • 旅程图:PR迭代先前工作或修复过去决策
  • 分层图:PR引入或改变具有不同级别的架构
  • 组合树:PR重构模块如何组合或委托给彼此
  • 文件重定位树:PR作为架构声明在目录之间移动文件
  • 比较表:PR引入替代方案或解释权衡
  • 流程图:PR改变数据或控制如何在组件之间移动

ASCII艺术字符使用:┌ ┐ └ ┘ ─ │ ├ ┤ ┬ ┴ ┼ ▼ ▲ ◀ ▶ ──→ ←── ⚠️ ✅ ❌

交错散文和视觉

不要让散文连续超过短段落而没有视觉中断。节奏应为:上下文 → 视觉 → 解释 → 视觉 → …

每个视觉(代码片段、ASCII图表、前后块)前应有1-3句上下文,并可选择后跟一句解释细微细节。如果您连续写超过4-5句散文,就错过了使用图表或代码块的机会。

良好节奏 — 散文和视觉自然交替:

[1-2句:问题是什么及其重要性]

\`\`\`typescript
// 显示新API的代码示例
workspace.extensions.sync.reconnect(directAuth('https://cloud.example.com'));
\`\`\`

\`\`\`
┌────────────────────────────┐
│  流程图显示步骤            │──► 逐步发生了什么
│  如何连接                  │
└────────────────────────────┘
\`\`\`

[1-2句:解释细微实现细节]

\`\`\`typescript
// 前后显示修复
\`\`\`

[1句:为什么之前有缺陷]

\`\`\`
┌──────────────────────────────────┐
│  架构图显示受影响部分            │
└──────────────────────────────────┘
\`\`\`

差节奏 — 长文本墙,视觉附加在末尾:

[段落解释问题]
[段落解释解决方案]
[段落解释实现细节]
[段落解释另一细节]

\`\`\`
[底部单图]
\`\`\`

读者眼睛应在散文和视觉之间跳跃。散文提供“为什么”,视觉提供“什么”和“如何”。两者都不应长期主导。

其他指南

  • 永远不要在PR标题/描述中包含Claude Code或opencode水印或归属
  • PR标题应遵循与提交相同的约定式提交格式
  • 专注于更改的“为什么”和“什么”,而不是“如何创建”
  • 突出显示任何破坏性更改
  • 链接到相关问题

验证GitHub用户名

关键:在PR描述、问题评论或任何GitHub内容中提及GitHub用户时,使用@用户名,永远不要猜测或假设用户名。始终使用GitHub CLI编程验证:

# 获取PR作者
gh pr view <PR编号> --json author

# 获取问题作者
gh issue view <问题编号> --json author

这避免了您错误赞扬他人的尴尬错误。在编写@提及前,始终运行验证命令。

合并策略

合并PR时,使用常规合并提交(非压缩):

gh pr merge --merge  # 正确:保留提交历史
# 错误:gh pr merge --squash
# 错误:gh pr merge --rebase

# 如果需要绕过分支保护,使用--admin标志
gh pr merge --merge --admin

保留个别提交;它们讲述工作如何演进的故事。

拉取请求正文格式

简单PR(单一目的更改)

使用干净段落格式:

第一段:解释更改做什么以及解决什么问题。

后续段落:解释实现如何工作。

示例

此更改为抽屉组件在内容超过可用抽屉高度时启用适当的垂直滚动。之前,长内容的抽屉可能会溢出而没有适当的滚动行为,使用户难以访问所有内容,并导致移动UX不佳。

为此,我将`{@render children?.()}`包装在`<div class="flex-1 overflow-y-auto">`容器中。`flex-1`类确保内容区域占据顶部固定拖动手柄后的所有剩余空间,而`overflow-y-auto`在内容高度超过可用空间时启用垂直滚动。

架构PR(API更改、结构重构)

对于更改API、存储结构或架构模式的PR,使用此章节顺序:

  1. 介绍段落(1-2):更改做什么以及为什么存在。如果适用,包括破坏性更改通知。以一句话决策摘要结尾:“我们选择X而不是Y,因为Z。”

  2. API迁移:显示新用法的前后代码示例。任何API更改必须包含。

  3. 存储/数据结构:显示任何结构更改前后布局的ASCII图表。

  4. 技术细节:扩展点、类型定义、配置格式——带代码示例。

  5. 理由:为什么选择此方法。旧方法设计为什么,为什么在实践中不行,新方法启用什么。

  6. 未来工作:什么可以稍后重新添加,什么被故意推迟。

  7. (可选)更改摘要/测试计划:如果包含,保持最小化并放在最后。

关键原则

  • 视觉优先:每个章节应以代码或ASCII图表领先;散文解释视觉
  • 代码片段和ASCII艺术最易扫描——突出它们
  • 理由仅散文(无需视觉);它解释思考
  • 完全跳过“更改”章节,或使其最小化在末尾

何时使用架构格式

  • 公共API形状更改(导出、签名、配置格式)
  • 持久数据格式更改(存储布局、迁移)
  • 跨包合同更改
  • 新子系统或重大重构

何时使用简单格式

  • 本地化修复/功能,无需消费者迁移
  • 行为保留的内部重构

语气和口吻

  • 对话但精确:像向同事解释一样写作
  • 直接诚实:“这很痛苦”而不是“这提出了挑战”
  • 展示您的思考:“我们考虑了X,但Y更有意义,因为…”
  • 团队决策用“我们”,个人观察用“我”

示例PR描述:

这修复了状态管理中嵌套响应性的长期问题。

首先,一些上下文:用户一直发现创建深度响应状态很繁琐。当前方法需要手动get/set属性,感觉不够Svelte-like。同时,我们想为了未来的性能优化远离对象突变,但`obj = { ...obj, x: obj.x + 1 }`很丑并产生开销。

此PR引入基于代理的响应性,让您编写惯用JavaScript:

```javascript
let todos = $state([]);
todos.push({ done: false, text: 'Learn Svelte' }); // 直接工作
```

底层,我们使用代理懒创建信号。这给我们突变的工效学与不可变的性能优势。

仍然TODO:
- 大型数组性能优化
- 文档更新
- 现有代码库迁移指南

这加倍了Svelte的编写更少、更直观代码的哲学,同时为计划在v6的细粒度响应性改进奠定基础。

要避免什么

  • 列出更改的文件:永远不要枚举哪些文件被修改。GitHub的“已更改文件”标签已显示此;PR描述应解释为什么,而不是什么文件
  • 顶部的“更改”章节:如果您需要更改摘要,放在最后并保持最小化。大多数PR不需要。
  • 测试计划:除非特别请求,跳过。测试应在代码中,而不是散文描述。
  • 要点或结构化列表(在简单PR中)
  • 章节标题如“## 摘要”或“## 所做更改”
  • 营销语言或过多格式
  • 企业语言:“此PR通过利用…增强我们的解决方案”
  • 营销话术:“游戏改变”、“革命性”、“无缝”
  • 戏剧性夸张:“感觉像永恒”、“痛点”、“折磨人”——坚持事实(“节省150ms”)而非戏剧
  • 过度解释简单更改
  • 为合理决策道歉语气

不要包含什么:

  • Generated with [Claude Code](https://claude.ai/code)
  • Co-Authored-By: Claude <noreply@anthropic.com>
  • 任何AI辅助引用
  • Generated with [opencode](https://opencode.ai)
  • Co-Authored-By: opencode <noreply@opencode.ai>
  • 工具归属或水印