Git 标签 – 永夜 https://www.shuijingwanwq.com 没有不值得去解决的问题,也没有不值得去学习的技术! Mon, 15 Jun 2026 10:19:38 +0000 zh-Hans hourly 1 https://wordpress.org/?v=7.0 GitHub Release 自动生成工作流实践流程 https://www.shuijingwanwq.com/2026/06/15/17125/ https://www.shuijingwanwq.com/2026/06/15/17125/#respond Mon, 15 Jun 2026 10:19:36 +0000 https://www.shuijingwanwq.com/?p=17125 Post Views: 3

引言

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

在整理项目 Release 的时候,发现每次都要手动编写 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

推送标签后,GitHub Actions 会自动生成如下格式的 Release:

🔧 实现步骤

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 支持
fixBug 修复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*"

核心流程

整个流程大概是这样的:

  1. Checkout:获取完整代码历史(fetch-depth: 0
  2. 生成 Release Notes
    • 获取上一个标签版本
    • 生成版本对比链接
    • 解析 commit 并按类型分类
    • 生成格式化的 Markdown 文档
  3. 创建 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 没有自动生成?

我遇到过这个问题,检查了几个方面:

  1. 标签名称是否以 v 开头(如 v1.0.0
  2. .github/workflows/release.yml 文件是否存在
  3. 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

项目时间长了之后,标签会越来越多,我一般会定期清理一些不再需要的旧标签,保持仓库整洁。


📚 参考链接


📝 总结

通过本文的配置,我已经实现了:

  1. 自动生成 Release 描述:基于 commit 历史自动分类
  2. 版本对比链接:自动生成与上一版本的对比
  3. 预发布版本支持:自动识别 beta/rc 版本
  4. 完全自动化:推送标签即可触发

现在你可以专注于代码开发,让 GitHub Actions 帮你处理 Release 的繁琐工作!🚀


如果你有任何问题或建议,欢迎在评论区留言讨论!


📌 相关文章

]]>
https://www.shuijingwanwq.com/2026/06/15/17125/feed/ 0