GitHub Release 自动生成工作流实践流程
引言
最近在整理项目 Release 的时候,发现每次都要手动编写 Release 描述,不仅繁琐还容易遗漏一些重要的变更。于是我花了一些时间研究了一下 GitHub Actions,配置了一套 Release 自动生成工作流。

用了一段时间后,感觉效果还不错,每次发布都变得轻松多了。今天就把我的实践过程分享给大家,希望能帮到有类似需求的朋友。
📋 效果预览
推送标签后,GitHub Actions 会自动生成如下格式的 Release:
## 📋 版本说明
📈 [查看变更对比](https://github.com/xxx/xxx/compare/v0.1.0...v1.0.0)
✨ 新功能
- **api** 添加百度翻译 API 支持
- **nginx** 支持自动生成 301 跳转规则
🐛 Bug 修复
- **csv** 修复中文 slug 解码问题
📝 文档更新
- **readme** 更新目录结构说明
---
🚀 发布于 2026/06/15

🔧 实现步骤
Step 1: 创建工作流文件
首先,我在项目根目录创建了 .github/workflows/release.yml 文件:
name: Release
on:
push:
tags:
- "v*"
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate Release Notes
run: |
echo "## 📋 版本说明" > /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
# 获取前一个标签
PREV_TAG=$(git tag --list 'v*' --sort=-v:refname | grep -A1 "${{ github.ref_name }}" | tail -n1)
if [ -n "$PREV_TAG" ]; then
COMPARE_URL="${{ github.event.repository.html_url }}/compare/${PREV_TAG}...${{ github.ref_name }}"
echo "📈 [查看变更对比]($COMPARE_URL)" >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
fi
# 初始化分类文件
> /tmp/feat.txt
> /tmp/fix.txt
> /tmp/docs.txt
> /tmp/refactor.txt
> /tmp/perf.txt
> /tmp/test.txt
> /tmp/chore.txt
> /tmp/style.txt
# 解析提交并分类
if [ -n "$PREV_TAG" ]; then
COMMITS=$(git log --oneline "$PREV_TAG"..HEAD)
else
COMMITS=$(git log --oneline)
fi
echo "$COMMITS" | while read -r COMMIT_LINE; do
# 提取类型、范围和描述
TYPE=$(echo "$COMMIT_LINE" | awk '{print $2}' | sed 's/^\([a-z]*\).*/\1/')
REST=$(echo "$COMMIT_LINE" | cut -d' ' -f3-)
# 提取范围(如果有)
SCOPE=$(echo "$REST" | sed -n 's/^\(([^)]*)\):.*/\1/p')
DESCRIPTION=$(echo "$REST" | sed 's/^([^)]*): //')
if [ -z "$DESCRIPTION" ]; then
DESCRIPTION="$REST"
fi
case "$TYPE" in
feat) echo "- ${SCOPE:+**$SCOPE** }$DESCRIPTION" >> /tmp/feat.txt ;;
fix) echo "- ${SCOPE:+**$SCOPE** }$DESCRIPTION" >> /tmp/fix.txt ;;
docs) echo "- ${SCOPE:+**$SCOPE** }$DESCRIPTION" >> /tmp/docs.txt ;;
refactor) echo "- ${SCOPE:+**$SCOPE** }$DESCRIPTION" >> /tmp/refactor.txt ;;
perf) echo "- ${SCOPE:+**$SCOPE** }$DESCRIPTION" >> /tmp/perf.txt ;;
test) echo "- ${SCOPE:+**$SCOPE** }$DESCRIPTION" >> /tmp/test.txt ;;
chore) echo "- ${SCOPE:+**$SCOPE** }$DESCRIPTION" >> /tmp/chore.txt ;;
style) echo "- ${SCOPE:+**$SCOPE** }$DESCRIPTION" >> /tmp/style.txt ;;
esac
done
# 输出分类结果
if [ -s /tmp/feat.txt ]; then
echo "✨ 新功能" >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
cat /tmp/feat.txt >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
fi
if [ -s /tmp/fix.txt ]; then
echo "🐛 Bug 修复" >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
cat /tmp/fix.txt >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
fi
if [ -s /tmp/docs.txt ]; then
echo "📝 文档更新" >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
cat /tmp/docs.txt >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
fi
if [ -s /tmp/refactor.txt ]; then
echo "🔧 代码重构" >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
cat /tmp/refactor.txt >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
fi
if [ -s /tmp/perf.txt ]; then
echo "⚡ 性能优化" >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
cat /tmp/perf.txt >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
fi
if [ -s /tmp/test.txt ]; then
echo "🧪 测试" >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
cat /tmp/test.txt >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
fi
if [ -s /tmp/chore.txt ]; then
echo "🔨 构建/工具" >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
cat /tmp/chore.txt >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
fi
if [ -s /tmp/style.txt ]; then
echo "🎨 代码风格" >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
cat /tmp/style.txt >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
fi
echo "---" >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
echo "🚀 发布于 $(date +'%Y/%m/%d')" >> /tmp/release_notes.md
echo "=== Generated Release Notes ==="
cat /tmp/release_notes.md
echo "=== End ==="
- name: Create Release
uses: softprops/action-gh-release@v2
with:
name: ${{ github.ref_name }}
body_path: /tmp/release_notes.md
draft: false
prerelease: ${{ contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }}
Step 2: 代码提交规范
这个工作流依赖 约定式提交(Conventional Commits)来分类变更。我一开始用的是比较随意的 commit 格式,后来发现这样生成的 Release Notes 效果不太好,就改成了规范的格式:
<类型>(<范围>): <描述>
类型说明:
| 类型 | 说明 | 示例 |
|---|---|---|
feat | 新功能 | feat(api): 添加百度翻译 API 支持 |
fix | Bug 修复 | fix(csv): 修复中文 slug 解码问题 |
docs | 文档更新 | docs(readme): 更新目录结构说明 |
refactor | 代码重构 | refactor(utils): 优化缓存逻辑 |
perf | 性能优化 | perf(cache): 减少 API 调用 |
test | 测试相关 | test(merge): 添加合并功能测试 |
chore | 构建/工具 | chore(docker): 更新 Dockerfile |
style | 代码风格 | style(go): 格式化 Go 代码 |
Step 3: 提交工作流文件
配置好工作流文件后,我提交到了仓库:
git add .github/workflows/release.yml
git commit -m "chore(release): 添加 Release 自动生成流程"
git push origin main
Step 4: 创建标签触发 Release
最后,创建一个标签来测试一下:
# 创建标签
git tag -a v1.0.0 -m "Release v1.0.0"
# 推送标签
git push origin v1.0.0
推送标签后,GitHub Actions 就会自动开始执行,几分钟后就能在 Releases 页面看到自动生成的 Release 了。
🔍 工作流原理
触发条件
我配置的工作流会在推送以 v 开头的标签时触发:
on:
push:
tags:
- "v*"
核心流程
整个流程大概是这样的:
- Checkout:获取完整代码历史(
fetch-depth: 0) - 生成 Release Notes:
- 获取上一个标签版本
- 生成版本对比链接
- 解析 commit 并按类型分类
- 生成格式化的 Markdown 文档
- 创建 Release:使用
softprops/action-gh-release创建 Release
自动分类逻辑
工作流会自动将 commit 按类型分组显示,这个逻辑是我用 bash 脚本实现的:
# 提取类型
TYPE=$(echo "$COMMIT_LINE" | awk '{print $2}' | sed 's/^\([a-z]*\).*/\1/')
# 分类输出
case "$TYPE" in
feat) echo "- ..." >> /tmp/feat.txt ;;
fix) echo "- ..." >> /tmp/fix.txt ;;
# ...
esac
一开始我试过用 GitHub 官方的自动生成功能,但发现不够灵活,最后还是自己写脚本实现了。
🎯 进阶配置
预发布版本支持
工作流会自动识别预发布版本:
prerelease: ${{ contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }}
示例:
v1.0.0-beta.1→ 预发布版本v1.0.0-rc.1→ 预发布版本v1.0.0→ 正式版本
这个功能我在发布测试版本时用得比较多,可以避免正式用户收到测试版本的更新通知。
添加自定义分类
如果需要添加自定义分类,可以修改 Generate Release Notes 步骤:
# 添加自定义分类
> /tmp/custom.txt
# 在 case 语句中添加
case "$TYPE" in
# ... 其他类型
custom) echo "- ${SCOPE:+**$SCOPE** }$DESCRIPTION" >> /tmp/custom.txt ;;
esac
# 在输出部分添加
if [ -s /tmp/custom.txt ]; then
echo "🔖 自定义分类" >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
cat /tmp/custom.txt >> /tmp/release_notes.md
echo "" >> /tmp/release_notes.md
fi
添加附件
如果需要在 Release 中添加二进制文件,可以添加以下步骤:
- name: Build
run: |
# 构建项目
go build -o myapp main.go
- name: Create Release
uses: softprops/action-gh-release@v2
with:
name: ${{ github.ref_name }}
body_path: /tmp/release_notes.md
files: |
myapp
我在 Go 项目中用这个功能自动编译并发布二进制文件,用户下载后直接就能运行,很方便。
🐛 常见问题
Q1: Release 没有自动生成?
我遇到过这个问题,检查了几个方面:
- 标签名称是否以
v开头(如v1.0.0) .github/workflows/release.yml文件是否存在- GitHub Actions 运行状态是否正常
Q2: Release Notes 内容为空?
原因: commit 消息不符合约定式提交格式。
解决方案: 使用规范的 commit 格式:
git commit -m "feat(api): 添加新功能" # ✅ 正确
git commit -m "add new feature" # ❌ 不推荐
我一开始也遇到过这个问题,后来改用了规范的 commit 格式就好了。
Q3: 如何更新已发布的 Release?
# 删除旧标签
git tag -d v1.0.0
git push origin :v1.0.0
# 创建新标签
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
Q4: 如何跳过自动 Release?
在 tag 名称中添加 no-release 后缀:
git tag -a v1.0.0-no-release -m "内部测试版本"
📊 最佳实践
1. 使用语义化版本控制
v<主版本号>.<次版本号>.<修订号>
- 主版本号:不兼容的 API 变更
- 次版本号:向后兼容的新功能
- 修订号:向后兼容的 Bug 修复
我刚开始没太注意版本号规范,后来发现用户反馈问题时,版本号不统一确实会带来一些麻烦,现在就严格按照这个规范来了。
2. 保持 commit 粒度适中
每个 commit 应该是一个独立的、可验证的变更单元:
# ✅ 推荐
git commit -m "feat(api): 添加用户注册接口"
# ❌ 不推荐
git commit -m "修改了很多东西"
我之前习惯把很多改动放在一个 commit 里,后来发现这样生成的 Release Notes 可读性很差,现在会尽量保持每个 commit 的粒度适中。
3. 定期清理旧标签
# 查看所有标签
git tag
# 删除本地标签
git tag -d v0.1.0
# 删除远程标签
git push origin :v0.1.0
项目时间长了之后,标签会越来越多,我一般会定期清理一些不再需要的旧标签,保持仓库整洁。
📚 参考链接
📝 总结
通过本文的配置,我已经实现了:
- ✅ 自动生成 Release 描述:基于 commit 历史自动分类
- ✅ 版本对比链接:自动生成与上一版本的对比
- ✅ 预发布版本支持:自动识别 beta/rc 版本
- ✅ 完全自动化:推送标签即可触发
现在你可以专注于代码开发,让 GitHub Actions 帮你处理 Release 的繁琐工作!🚀
如果你有任何问题或建议,欢迎在评论区留言讨论!
📌 相关文章