Issues 工作流
文档用途
这篇文档用于介绍基于 issue 的协作方式.
包括 github 和 gitlab 两个主流的平台上的配置和使用.
下图为 github issue 的真实案例, 来自于开源游戏引擎 bevy.

相关链接
参考 GitLab Issues 官方文档 和 GitHub Issues 官方文档.
另外 Issues 经常与 Merge Request 和 Pull Requests 结合使用.
术语约定
| 英文 | 中文 | 含义 |
|---|---|---|
issue | 我们直接使用英文术语 (比较接近的翻译是: 议题, 单) | |
open / closed | 用于表示 issue 的状态, 我们约定为 开着 和 已关闭 | |
repository | 仓库 | 为了便于特指, 我们约定将其称为 代码仓库 或者 远程仓库 |
label | 标签 | 我们约定在 issue 相关语境下的 标签 都是指 label |
assign | 分派 | 与之对应的名词 assignee 可以翻译为 受托人 |
git commit SHA | 就是某个 commit 的编号, 在单个代码仓库中唯一 | |
git commit message | 既包括必定需要写的第一行, 也包括可选的更新细致的描述 |
如下图所示, 通过 git log 命令可以查看 SHA 和 message.

工作流示意图
issue 可以有多种工作流, 这里举例说明其中一种比较常见的.
- 💡在有新点子或意识到有问题需要解决的时候, 创建 issue.
- 🧠新建的 issue 可能不完善, 需要进一步做设计, 并修改标题和描述等信息.
- 💦当设计足够清晰后, 则动手执行, 执行过程中可能有新想法, 则持续改进设计.
- 📝追踪最终成果和阶段性成果, 以及遇到的问题和解决方案.
适用场景
在涉及到具体操作前, 我们需要搞明白 "什么时候应该新建 issue".
满足下列 任意 条件, 则应当新建 issue:
- 某项工作超过半小时才能完成.
- 参与者不是自己或不只有自己.
- 暂时不打算做, 先记下来免得忘了.
- 寻求帮助, 开启讨论, 反馈问题.
- 进度或结果需要被追踪.
也可以这么想: 默认情况下应创建 issue, 除非有充足的理由不这么做.
(例如为了避免 "新建 issue 这个操作反而成了工作的阻碍").
Issue 的优势
可追溯
某个 bug 被解决了, issue 被关闭. 后面如果出现相同的 bug, 则可以再次开启曾经的 issue.
曾经实现了某个 feature, issue 被关闭. 之后想看看当初针对这个 feature 的设计, 还能再找到.
通过查看 issue 关闭事件的发生时间, 可以得知其何时被关闭.
讨论与跟进
实现某个 feature 时, 可能需要讨论方案, issue 就可以直接当作一个论坛帖子.
针对这个 issue 的操作都会显示在时间轴上, 包括且不限于: 开关, 编辑, 提及, 关联.
如下图所示, 单个 issue 的时间轴上会有各种相关事件.

与 git 结合
在 commit message 中就可以 提及 或 关闭 某个 issue:
在 issue 的时间轴上能看到来自于 git 的操作, 并且包含 commit SHA.
如果想提及某个 issue, 可以直接写对应 issue 的编号, 例如 #6.
如果想关闭某个 issue, 可以一些关键词, 例如 close #6.
"通过 commit message 来关闭 issue" 被称之为 Closing Pattern, 详见官方文档:
Manage issues - GitLab 以及 Linking a pull request to an issue - GitHub.
Closing Pattern 示例
例如有一条 commit message 如下, 效果是:
提及了 #17 (但没有关闭), 关闭了 #18 和 #19.
Awesome commit message
This commit is related to #17 and fixes #18, #19.需求的来源
issue 可以作为 唯一的需求来源:
只需要打开 github 或 gitlab 就能看到所有分派给自己的 issue.
在具体代码仓库中的 issue 界面, 可以看到当前项目的所有 issue (无论受托人是否是自己).
可以在 gitlab 主页看到 Issues 栏的数字, 这代表着自己是多少个 issue 的受托人.
GitLab 中按下快捷键 shift+i 在打开的页面中能看到所有分派给自己的 issue.

在 GitHub 中 按下快捷键 g i 进入个人的 issue 界面.

版本规划
在规划版本时, 我们希望能明确单个版本的研发需求, 通常会优先实现即将发布版本的需求.
此时可以借助与 issue 紧密相关的 milestone 功能, 将一件件 issue 与版本里程碑关联.
这样就能清晰的看到版本进度, 以及还剩那些 issue 关闭后就能发布版本.
下图为某个 github 开源项目的 milestone.

除了以版本作为 milestone, 还可以有其他用途.
例如这个开源项目设置了名为 Backlog 的 milestone, 用于记录未被列入版本计划的 issue.

新建 Issue
标题
对于 issue 而言最重要的就是恰当的标题, 应当一目了然, 简短且信息密度大.
不适合放入标题但需要说明的部分, 应该放在描述中.
TIP
在标题中使用 emoji 更有助于快速理解 issue 标题.
Windows 中使用 Win+;, macOS 中使用 Ctrl+Command+Space 输入 emoji.
| 类型 | emoji | 类型 | emoji | |
|---|---|---|---|---|
| 讨论/设计 | 💡 | 调研 | 🔍 | |
| bug | 🐛 | feature | 🧩 | |
| 紧急事件 | 🔥 | 文档 | 📄 | |
| 音乐音效 | 🎵 | 运维 | 🔧 | |
| 画面 | 🎬 | package | 📦 | |
| 安全性 | 🛡️ | 反馈 | ✏️ |
描述
描述部分建议恰当使用 GitLab 风格的 Markdown 语法, 使得描述的结构清晰.
如果 issue 可以被拆分成更具体的待办事项, 则建议使用下图中的 checklist.

由于 checklist 很常用, 因此 GitLab 和 GitHub 都对其做了特殊处理:
在 Issues 列表页面, 可以看到待办事项的数量和进度.


INFO
只有位于 "Issue 描述" 中的 checklist 才会被统计, 评论中的不算.
快捷键
github 和 gitlab 都可以通过 shift+/ 查看当前界面所有可用的快捷键.
这里仅列出与 issue 相关的少量快捷键.
| 操作 | github | gitlab |
|---|---|---|
| 新建 issue | 在代码仓库的 issue 页面内按 c | 在代码仓库内按 i |
| 查看当前代码仓库的所有 issue | 连续按下 g i | 连续按下 g i |
| 查看所有和自己相关的 issue | 在代码仓库外按下 g i | 在任意界面 shift+i |
编辑技巧

GitLab 中编辑文本的快捷键:
| 快捷键 | 条件 | 功能 |
|---|---|---|
ctrl+shift+p | 编辑文本时 | 切换编辑模式/预览模式 |
ctrl+k | 编辑文本时 | 插入链接 []() |
所有输入框都支持 markdown 语法.
使用 / 符号可以实现快速操作, 例如:
批量设置 label, 设置受托人, 设置 milestone, 将当前 issue 移动至其他代码仓库.
详见 gitlab 官方文档的 quick action 部分.
详见 github 官方文档的 slash commands 部分.
模板
我们希望能快速创建 issue, 而同类型的 issue 通常有类似的结构, 此时模板将会有帮助.
因为所有 issue 都是 markdown 格式, 因此模板都是 .md 文件.
模板文件在代码仓库中的路径:
/.github/ISSUE_TEMPLATE//.gitlab/issue_templates/在 github 中创建 issue 时, 如果存在模板文件, 则会自动出现选择模板的界面.
另外也可以在编辑 issue 时使用 /template 这个斜杠命令来选择模板.

在 gitlab 中创建 issue 时, 可以在 Description 中选择一个模板.

Bug Report 模板示例
## 观察到的现象
(请描述异常现象)
## 预期效果
(请描述符合预期的效果)\
(若不确定预期效果应该是什么, 则说明为何认为观察到的现象是 bug)
## 如何复现
(按照所描述的步骤操作, 即可复现观察到的现象)\
(如果不能百分百复现, 请说明概率以及在什么情况下更容易复现)
## 发现问题的版本
(例如: 首次观察到此 Bug 的 Git SHA 为 xxx)
## 可能的原因
(可选)\
(对 bug 起因的猜测, 或是可能和什么相关)
## 补充信息
(可选)\
(例如截图和日志)
/label ~"c-bug"操作 Issue
开启与关闭
当一个 issue 创建时, 默认就是开启状态.
为了关闭 issue, 可以在这个 issue 对应的网页手动关闭, 也可以使用 closing pattern.
当手动关闭时, 要记得说明为何关闭 issue.
如下图所示, 说明关闭 issue 的原因后, 再点击
Comment & close issue按钮.

除了关闭, 还可以删除 issue, 但是绝大多数情况下我们都不应该这么做.
因为 issue 一旦被删除, 就不可追溯, 所有与之相关的数据都会消失,
并且 issue 编号会被释放, 被释放的编号在之后创建新 issue 时可会被使用, 引起更多错误.
考虑删除 issue 的场景: 错误创建的 issue, 为了实践 issue 功能而创建的临时 issue.
添加附件
常见的附件包括 图片 和 日志. 我们只需要将附件拖拽进 issue 的输入框.
插入图片时, 默认尺寸通常会偏大, 此时可以借助 GitLab Flavored Markdown 语法来设置.
<!-- 以默认尺寸显示图片 -->

<!-- 等比缩放, 让宽度显示为页面的 20% -->
{width=20%}
<!-- 等比缩放, 将高度设置为 300 像素 -->
{height=300px}TIP
GitLab Issue 中可以直接播放视频, 无需将视频转换为动图再上传.
关联与重复
github 和 gitlab 都提供了用于表达 issue 之间的联系的功能.
例如为了实现一项功能, 可能需要代码逻辑和视听效果功能配合.
此时可能会单独创建 代码逻辑, 美术素材, 音频素材 这几 issue, 并相互关联.
gitlab 中使用 /relate 斜杠命令可以方便地关联其他 issue.

还可以在已经创建好的 issue 页面中找到 Linked items 方框, 点击右上角的 Add 按钮来关联.

TIP
只需要执行一次关联操作, 就能影响两个 issue.
因此在两个相互关联的 issue 页面 Linked items 方框中, 能看到彼此.
github 中直接使用 issue 编号即可创建关联. 没有对应的斜杠命令 (也不需要).
没有类似于 gitlab 的 Linked items 方框, 只能在时间轴事件上看到.

如果需要关联到其他代码仓库中的 issue,
在相同 group 的情况下, 可以用 project#xxx 来关联.
在 group 不同的情况下, 通过完整的 url 来关联.
类似与 issue 关联类似的概念是 重复.
显然对于相同的需求, 只应该追踪唯一的 issue.
当我们发现 issue 有重复时, 则应该将重复的 issue 标记为重复并关闭.
gitlab 中使用 /duplicate 斜杠命令, 将会当前 issue 标记为重复, 并且关闭当前 issue.
gitlab 在声明重复时会自动关闭 issue, 但 github 不会.

github 如果希望标记重复的 issue, 则需要使用 Duplicate of #xxx.
注意 Duplicate 的首字母 D 需要大写.

添加引用
Issue 可以通过 #xxx 的形式被引用 ( # 作为前缀).
如果需要跨仓库引用, 则需要在前面添加 group 和仓库名称, 例如 group/project#123.
如果两个仓库位于相同 group 内, 则可以忽略 group 名称, 并简写为 project#123.
TIP
当位于某个 issue 或 merge request 页面时, 用快捷键 c r 可以复制引用.
GitLab 提供了特殊语法来详细显示对于 Issue 的引用: 在末尾加上 + 或 +s.
假设某个 Issue 编号为 #22, 已经被关闭, 分派给了 Yusong, milestone 为 0.1:
| 原文 | 显示效果 |
|---|---|
#1 | #22 (closed) |
#1+ | 支持动态分辨率 (#22 - closed) |
#1+s | 支持动态分辨率 (#22 - closed) • Yusong • 0.1 |
TIP
在 gitlab wiki 中可以使用这种方法来引用 issue.
全局搜索
如果想要查询某个 Issue, 则可以在 Issue 浏览页面设置筛选条件.
然而笔者更推荐使用全局搜索, 使用快捷键 / 唤出, 然后直接搜索 Issue 标题的关键字:

在 GitHub 中也有类似的全局搜索功能.
使用快捷键 Ctrl+K 唤出, 输入 # 以及 Issue 标题的关键字:

TIP
处于某个具体的代码仓库时, 对 issue 的全局搜索会更准确.

Assignee
assignee, 受托人.
每个 issue 都最多可以指定一个受托人, 但也可以不指定.
我们在实际使用中, 如果能够确定某个 issue 由谁来完成, 则应该指定受托人.
如果还不能确定谁来完成, 则暂时不设置.
我们可以在 issue 页面通过表达式搜索为分派的 issue:
下图为 gitlab issue 页面, 表达式提供丰富的筛选功能.

我们可以在 Milestone 中一眼看到未设置受托人的 issue.
如果为分派的 issue 数量不多, 这种方式更方便.

Milestone
milestone, 里程碑.
在上文的 版本规划 部分已经介绍了.
GitLab 中在任意界面使用快捷键 shift+m 即可查看所有参与项目的 milestone.
GitHub 中没有直接查看 mitlestone 的快捷键, 但可以通过 g i 进入 issue 页面再跳转.
To-do List
随着 issue 数量的增加, 会更难以发现新增的 issue 及其进展.
此时我们需要一个类似于通知的功能, 当发生值得注意的变化时就发送通知.
GitLab 提供了这项功能, 名为 To-do List.
INFO
GitHub 没有 To-do List, 但会收到 Notification.
如下图所示, 在 GitLab 主页面的导航栏能看到 To-Do List.
在任意界面使用快捷键 shift+t 即可进入 To-Do List 页面.

To-do List 可能会因为以下原因被添加.
其他触发条件详见 gitlab 官方文档: To-Do List | GitLab.
- 自己被指定为某个 issue 的受托人.
- 在某个 issue 中被
@提及. - 有个需要自己处理的 merge request.
- 自己提交的 merge request 未能顺利通过 CI/CD.
TIP
记得手动勾选已经处理的 To-do List 项.
不及时勾选已经处理的项, 就会让 To-do List 失去时效性.
时间追踪
时间追踪 (time tracking) 具体的功能是:
针对完成某个 issue 或 merge request 的时间, 做 "估计, 追踪, 统计".
只有 GitLab 支持此功能, 详见官方文档 Time tracking - GitLab Docs.
我们可以使用 quick action 来设置 "预估时间" 与 "花费的时间"./estimate 对应 "预估时间", /spend 对应 "花费的时间" (他们可以分别设置).
另外如果之后希望移除 "预估时间", 可以使用 /remove_estimate.
所有可以使用的时间单位
| Time unit | What to type | Conversion rate |
|---|---|---|
| Month | mo / month / months | 4 w (160 h) |
| Week | w / week / weeks | 5 d (40 h) |
| Day | d / day / days | 8 h |
| Hour | h / hour / hours | 60 m |
| Minute | m / minute / minutes |
时间的显示效果
默认情况下, 时间会尝试以 最大单位 显示 (例如 1d 4h 10m).
如果希望让时间始终以 "小时" 为单位显示 (例如 12h 10m), 则应做如下设置:
在 Admin Area / Settings / Preferences 中勾选 Limit display of time tracking units to hours.
TIP
使用 /spend 时可以写入负数 (例如 /spend -20m), 效果是减少 "已花费的时间".
这种操作的适用场景: 前面不小心多记录了一些时间, 想要通过这种方式来修正.
在 issue 列表页面, 可以预览 "预估时间":

在某个具体的 issue 页面的右侧, 可以看到时间追踪相关的数据.
预估时间与花费的时间在技术上没什么联系, 可以只设置其中一个或是设置不同的值:

点击上图中的 Time tracking report 可以看到 "花费的时间" 的记录.
这里只能看到 单个 issue 或 merge request 的记录, 如果希望能看到全局的记录,
就需要启用名为 global_time_tracking_report 的 feature flag.
启用后, 就可以在 https://[your-domain]/-/timelogs 页面看到:
所有人的 "花费时间的记录" (既包括每次的具体信息, 也有统计数据, 还可以做筛选).
另外时间追踪的结果也会体现在 Milestone 中 (显示在右侧边栏).
因此, 如果我们为与某个 milestone 关联的每个 issue 都添加时间追踪,
那么就可以统计完成某个 milestone 总共花费的时间.
标签 Label
设计
此文档开头的图片就是非常好的标签设计和使用示范.
另外也非常推荐学习 rust-lang/rust 的标签用法.

通过标签可以快速了解某个 issue 大体和什么有关.
还能能通过标签筛选 (在 issue 数量多的情况下尤其重要).
同时我们需要合理设计, 使得 "给 issue 贴标签" 不成为负担.
为此, 我们设计一些 相互正交的概念, 每个概念对应一组标签.
| 概念 | 英文 | 前缀 | 举例 |
|---|---|---|---|
| 类型 | category | c | c-bug c-feature c-doc c-enhancement |
| 领域 | area | a | a-graphics a-audio a-network a-qol |
| 操作系统 | OS | o | o-windows o-linux o-android o-ios |
| 优先级 | priority | p | p-critical p-high p-low |
| 状态 | state | s | s-blocked s-wontfix s-needs-design s-wip |
| 工具 | tool | t | t-blender t-gitlab t-azure t-xcode |
| 难度 | difficulty | d | d-complex d-good-first-issue |
每个概念都对应一组标签:
游戏项目中的 Label 示例
- c 灰色 [必选-issue 的类型] bug (橙色), doc, feature, test, help, example, enhancement, research, design (深蓝色), discussion (黄色), content.
- a 绿色 [必选-领域, 仅限客户端] gameplay, meta, audio, graphics, physics, assets, input, text, hotfix, network, 2d, 3d, localization, qol
- a 浅绿 [可选-领域] web, client, server, cicd, dev-ops, cli, workflow, performance
- o 蓝色 [当不是全平台时必选] unity-editor, ios, android, windows, macos, linux, docker, k8s
- p 红色 [可选-优先级] critical, high (橙色)
- s 紫色 [可选-状态] blocked, wontfix, needs-design, wip (青色).
- t 白色 [可选-工具] unity, blender, fmod, logic-pro, android-studio, xcode, gitlab, github, aliyun, azure, cloudflare
预设
在给某个 issue 添加标签时, 只能从预设的标签中选择.
任意代码仓库都会继承并同步 GitLab Group 的标签预设.
如果希望在多个代码仓库中复用标签预设, 直接编辑 Group 的标签会比较方便.
如果只希望针对单个代码仓库, 则应该编辑此仓库的标签预设.
GitHub 中给组织设置新项目的默认 Label:
settings / repository / repository defaults
WARNING
GitHub 给组织设置 Label 的功能有缺陷: 无法和组织内的项目同步标签.
GitHub Organization 预设好标签之后, 再创建远程仓库, 才能继承标签预设.
如果GitHub Organization 的预设标签发生改动, 远程仓库也不会同步.
编辑
为了快速且恰当地标记标签, 推荐综合使用以下方案:
- 新建 issue 时使用模板.
- 使用
/斜杠命令. - 使用快捷键.
当需要新建 issue 或批量设置 label 时, / 斜杠命令最好用.

当需要修改已创建的 issue 标签时, 可以在具体的 issue 页面使用快捷键 l.
