♻️ 重构`changelog_and_release.yml`
Hello from ImmortalWrt / say-hello (push) Successful in 0s Details

This commit is contained in:
csh 2025-11-03 10:40:26 +08:00
parent 9961226704
commit 1f535e0675
1 changed files with 358 additions and 254 deletions

View File

@ -5,6 +5,10 @@ on:
tags: tags:
- "[0-9]*" - "[0-9]*"
concurrency:
group: release-${{ github.repository }}
cancel-in-progress: false
# ========================================== # ==========================================
# 🔧 配置区域 - 根据你的项目修改 # 🔧 配置区域 - 根据你的项目修改
# ========================================== # ==========================================
@ -33,7 +37,7 @@ env:
# ===== 分支配置 ===== # ===== 分支配置 =====
# 主分支名称(用于推送 CHANGELOG 更新) # 主分支名称(用于推送 CHANGELOG 更新)
# 如果留空会自动检测main 或 master # 如果留空会自动检测main 或 master
MAIN_BRANCH: "" MAIN_BRANCH: "main"
# ===== 服务器配置 ===== # ===== 服务器配置 =====
# Gitea 服务器地址(用于生成头像链接和 API 调用) # Gitea 服务器地址(用于生成头像链接和 API 调用)
@ -63,6 +67,8 @@ env:
# - "true": 创建为草稿,不会立即发布 # - "true": 创建为草稿,不会立即发布
# - "false": 立即发布 Release # - "false": 立即发布 Release
RELEASE_IS_DRAFT: "false" RELEASE_IS_DRAFT: "false"
# 统一管理 pre-release 关键词
PRERELEASE_KEYWORDS: "alpha|beta|rc|pre|preview|dev|test"
# 额外要上传到 Release 的文件(空格分隔) # 额外要上传到 Release 的文件(空格分隔)
# 例如: "README.md LICENSE docs/guide.pdf" # 例如: "README.md LICENSE docs/guide.pdf"
ADDITIONAL_RELEASE_FILES: "" ADDITIONAL_RELEASE_FILES: ""
@ -137,10 +143,14 @@ jobs:
# 从环境变量读取配置 # 从环境变量读取配置
VERSION="${{ github.ref_name }}" VERSION="${{ github.ref_name }}"
KEYWORDS="${{ env.PRERELEASE_KEYWORDS }}"
STRIP_REGEX="-($KEYWORDS)[0-9]*$" # 用于 sed 删除后缀
MATCH_REGEX="($KEYWORDS)" # 用于 bash =~ 匹配
# 处理 CHANGELOG 版本号 # 处理 CHANGELOG 版本号
if [[ "${{ env.CHANGELOG_VERSION_MODE }}" == "strip" ]]; then if [[ "${{ env.CHANGELOG_VERSION_MODE }}" == "strip" ]]; then
# 去除 pre-release 后缀(-beta, -rc1, -alpha 等) # 去除 pre-release 后缀(-beta, -rc1, -alpha 等)
CHANGELOG_VERSION=$(echo "$VERSION" | sed -E 's/-(alpha|beta|rc|pre|preview|dev|test)[0-9]*$//') CHANGELOG_VERSION=$(echo "$VERSION" | sed -E "s/${STRIP_REGEX}//")
echo "📝 CHANGELOG version mode: strip" echo "📝 CHANGELOG version mode: strip"
echo " Tag: $VERSION → CHANGELOG: $CHANGELOG_VERSION" echo " Tag: $VERSION → CHANGELOG: $CHANGELOG_VERSION"
else else
@ -160,7 +170,7 @@ jobs:
# 智能判断 pre-release # 智能判断 pre-release
if [[ "${{ env.RELEASE_PRERELEASE_MODE }}" == "auto" ]]; then if [[ "${{ env.RELEASE_PRERELEASE_MODE }}" == "auto" ]]; then
if [[ "$VERSION" =~ (alpha|beta|rc|pre|preview|dev|test) ]]; then if [[ "$VERSION" =~ $MATCH_REGEX ]]; then
RELEASE_IS_PRERELEASE="true" RELEASE_IS_PRERELEASE="true"
PRERELEASE_DETECTED="yes (auto-detected: $VERSION contains pre-release keyword)" PRERELEASE_DETECTED="yes (auto-detected: $VERSION contains pre-release keyword)"
else else
@ -172,12 +182,6 @@ jobs:
PRERELEASE_DETECTED="${{ env.RELEASE_PRERELEASE_MODE }} (manually configured)" PRERELEASE_DETECTED="${{ env.RELEASE_PRERELEASE_MODE }} (manually configured)"
fi fi
# 导出处理后的配置到 GitHub 环境变量
echo "CHANGELOG_VERSION=$CHANGELOG_VERSION" >> $GITHUB_ENV
echo "RELEASE_TITLE_PROCESSED=$RELEASE_TITLE_PROCESSED" >> $GITHUB_ENV
echo "RELEASE_IS_PRERELEASE=$RELEASE_IS_PRERELEASE" >> $GITHUB_ENV
echo "COMMIT_MSG=$COMMIT_MSG" >> $GITHUB_ENV
# 处理忽略模式(转换为数组) # 处理忽略模式(转换为数组)
IFS='|' read -ra PATTERNS_ARRAY <<< "${{ env.IGNORE_PATTERNS }}" IFS='|' read -ra PATTERNS_ARRAY <<< "${{ env.IGNORE_PATTERNS }}"
IGNORE_JSON="[" IGNORE_JSON="["
@ -189,17 +193,27 @@ jobs:
fi fi
done done
IGNORE_JSON="${IGNORE_JSON%,}]" IGNORE_JSON="${IGNORE_JSON%,}]"
# 导出处理后的配置到 GitHub 环境变量
echo "CHANGELOG_VERSION=$CHANGELOG_VERSION" >> $GITHUB_ENV
echo "RELEASE_TITLE_PROCESSED=$RELEASE_TITLE_PROCESSED" >> $GITHUB_ENV
echo "RELEASE_IS_PRERELEASE=$RELEASE_IS_PRERELEASE" >> $GITHUB_ENV
echo "COMMIT_MSG=$COMMIT_MSG" >> $GITHUB_ENV
echo "PRERELEASE_STRIP_REGEX=$STRIP_REGEX" >> $GITHUB_ENV
echo "PRERELEASE_MATCH_REGEX=$MATCH_REGEX" >> $GITHUB_ENV
echo "IGNORE_PATTERNS_JSON=$IGNORE_JSON" >> $GITHUB_ENV echo "IGNORE_PATTERNS_JSON=$IGNORE_JSON" >> $GITHUB_ENV
# 显示配置摘要 # 显示配置摘要
echo "" echo ""
echo "📋 Configuration Summary:" echo "📋 Configuration Summary:"
echo " 🏷️ Tag version: $VERSION" echo " 🏷️ Tag version: $VERSION"
echo " 📝 CHANGELOG version: $CHANGELOG_VERSION" echo " 📝 CHANGELOG version: $CHANGELOG_VERSION"
echo " 🌐 Gitea Server: ${{ env.GITEA_SERVER }}" echo " 🌐 Gitea Server: ${{ env.GITEA_SERVER }}"
echo " 📄 CHANGELOG Title: ${{ env.CHANGELOG_SECTION_TITLE }}" echo " 📄 CHANGELOG Title: ${{ env.CHANGELOG_SECTION_TITLE }}"
echo " 🚀 Release Title: $RELEASE_TITLE_PROCESSED" echo " 🚀 Release Title: $RELEASE_TITLE_PROCESSED"
echo " 🏷️ Pre-release: $PRERELEASE_DETECTED" echo " 🏷️ Pre-release: $PRERELEASE_DETECTED"
echo " 📄 Draft: ${{ env.RELEASE_IS_DRAFT }}" echo " 📄 Draft: ${{ env.RELEASE_IS_DRAFT }}"
echo " 💬 Commit Message: $COMMIT_MSG" echo " 💬 Commit Message: $COMMIT_MSG"
echo " 📎 Additional Files: ${ADDITIONAL_RELEASE_FILES:-none}" echo " 📎 Additional Files: ${ADDITIONAL_RELEASE_FILES:-none}"
@ -259,6 +273,13 @@ jobs:
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "❌ 克隆失败" echo "❌ 克隆失败"
cat /tmp/git_clone.log
# 清理残留
if [ -d "$REPO_DIR" ]; then
rm -rf "$REPO_DIR"
fi
exit 1 exit 1
fi fi
@ -283,26 +304,37 @@ jobs:
fi fi
fi fi
# 检测主分支名称
echo "" echo ""
echo "🔍 检测主分支名称..." echo "🔍 验证主分支配置..."
if [ -n "${{ env.MAIN_BRANCH }}" ]; then
MAIN_BRANCH="${{ env.MAIN_BRANCH }}" # 检查是否配置了主分支
echo "✓ 使用配置的主分支: $MAIN_BRANCH" if [ -z "${{ env.MAIN_BRANCH }}" ]; then
else echo "❌ 错误: 未配置主分支"
# 自动检测 main 或 master echo ""
if git show-ref --verify --quiet refs/remotes/origin/main; then echo "请在 workflow 配置文件的 env 区域设置 MAIN_BRANCH:"
MAIN_BRANCH="main" echo ""
echo "✓ 自动检测到主分支: main" echo "env:"
elif git show-ref --verify --quiet refs/remotes/origin/master; then echo " MAIN_BRANCH: \"main\" # 或 \"master\""
MAIN_BRANCH="master" echo ""
echo "✓ 自动检测到主分支: master" exit 1
else
echo "❌ 错误: 无法检测主分支main 或 master"
exit 1
fi
fi fi
MAIN_BRANCH="${{ env.MAIN_BRANCH }}"
echo "✓ 使用配置的主分支: $MAIN_BRANCH"
# 验证分支是否存在
if ! git show-ref --verify --quiet refs/remotes/origin/$MAIN_BRANCH; then
echo "❌ 错误: 配置的分支 '$MAIN_BRANCH' 不存在"
echo ""
echo "可用的远程分支:"
git branch -r | grep -v HEAD
echo ""
echo "请在 workflow 配置文件中修改 MAIN_BRANCH 为正确的分支名"
exit 1
fi
echo "✓ 分支 '$MAIN_BRANCH' 已验证存在"
# 切换到主分支 # 切换到主分支
echo "" echo ""
echo "🌿 切换到主分支: $MAIN_BRANCH" echo "🌿 切换到主分支: $MAIN_BRANCH"
@ -346,38 +378,9 @@ jobs:
echo "======================================" echo "======================================"
echo "" echo ""
- name: 🔍 检查版本是否已存在
id: check_version
if: steps.check_bot.outputs.is_bot_commit != 'true'
run: |
echo "======================================"
echo "🔍 检查 CHANGELOG 版本"
echo "======================================"
cd ${{ env.REPO_DIR }}
if [ ! -f "CHANGELOG.md" ]; then
echo "📝 CHANGELOG.md 不存在,将创建新文件"
echo "version_exists=false" >> $GITHUB_OUTPUT
else
CHANGELOG_VERSION="${{ env.CHANGELOG_VERSION }}"
echo "📝 检查版本: $CHANGELOG_VERSION"
if grep -q "## :bookmark: $CHANGELOG_VERSION" CHANGELOG.md; then
echo "⚠️ 版本 $CHANGELOG_VERSION 已存在于 CHANGELOG.md"
echo "version_exists=true" >> $GITHUB_OUTPUT
else
echo "✅ 版本 $CHANGELOG_VERSION 不存在,可以继续"
echo "version_exists=false" >> $GITHUB_OUTPUT
fi
fi
echo "======================================"
echo ""
- name: 📝 生成 CHANGELOG - name: 📝 生成 CHANGELOG
id: changelog id: changelog
if: steps.check_bot.outputs.is_bot_commit != 'true' && steps.check_version.outputs.version_exists != 'true' if: steps.check_bot.outputs.is_bot_commit != 'true'
run: | run: |
echo "======================================" echo "======================================"
echo "📝 生成 CHANGELOG" echo "📝 生成 CHANGELOG"
@ -407,7 +410,7 @@ jobs:
# 对每个 tag 执行 strip 操作(去除 pre-release 后缀) # 对每个 tag 执行 strip 操作(去除 pre-release 后缀)
if [[ "${{ env.CHANGELOG_VERSION_MODE }}" == "strip" ]]; then if [[ "${{ env.CHANGELOG_VERSION_MODE }}" == "strip" ]]; then
tag_stripped=$(echo "$tag" | sed -E 's/-(alpha|beta|rc|pre|preview|dev|test)[0-9]*$//') tag_stripped=$(echo "$tag" | sed -E "s/${{ env.PRERELEASE_STRIP_REGEX }}//")
else else
tag_stripped="$tag" tag_stripped="$tag"
fi fi
@ -420,248 +423,353 @@ jobs:
done <<< "$ALL_TAGS" done <<< "$ALL_TAGS"
if [ -z "$PREVIOUS_TAG" ]; then if [ -z "$PREVIOUS_TAG" ]; then
echo " No previous tag found, using all commits" echo " 未找到前一个tag将使用所有提交"
COMMIT_RANGE="" COMMIT_RANGE="HEAD"
else else
echo "📍 Previous tag: $PREVIOUS_TAG" echo "📍 前一个tag: $PREVIOUS_TAG"
# 对 previous tag 也执行 strip显示对应的 CHANGELOG 版本 # 对 previous tag 也执行 strip显示对应的 CHANGELOG 版本
if [[ "${{ env.CHANGELOG_VERSION_MODE }}" == "strip" ]]; then if [[ "${{ env.CHANGELOG_VERSION_MODE }}" == "strip" ]]; then
PREV_CHANGELOG_VERSION=$(echo "$PREVIOUS_TAG" | sed -E 's/-(alpha|beta|rc|pre|preview|dev|test)[0-9]*$//') PREV_CHANGELOG_VERSION=$(echo "$PREVIOUS_TAG" | sed -E "s/${{ env.PRERELEASE_STRIP_REGEX }}//")
echo " (对应 CHANGELOG 版本: $PREV_CHANGELOG_VERSION)" echo " (对应 CHANGELOG 版本: $PREV_CHANGELOG_VERSION)"
fi fi
COMMIT_RANGE="${PREVIOUS_TAG}..${VERSION}" COMMIT_RANGE="${PREVIOUS_TAG}..${VERSION}"
fi fi
# 获取提交记录
echo "" echo ""
echo "📋 获取提交记录..." echo "🔍 扫描提交记录..."
if [ -z "$COMMIT_RANGE" ]; then
git log --pretty=format:'%H|%s' > /tmp/commits.txt # ============================================
else # 使用 NULL 分隔符读取 git log (最安全可靠的方式)
git log --pretty=format:'%H|%s' "$COMMIT_RANGE" > /tmp/commits.txt # ============================================
declare -a COMMITS_HASH
declare -a COMMITS_AUTHOR
declare -a COMMITS_MESSAGE
declare -a COMMITS_BODY
# 从环境变量加载忽略模式 (转换为 bash 数组)
IFS='|' read -ra IGNORE_PATTERNS <<< "${{ env.IGNORE_PATTERNS }}"
# 去除每个模式的首尾空格
for i in "${!IGNORE_PATTERNS[@]}"; do
IGNORE_PATTERNS[$i]=$(echo "${IGNORE_PATTERNS[$i]}" | xargs)
done
echo "📋 已加载 ${#IGNORE_PATTERNS[@]} 个忽略模式"
VALID_COUNT=0
SKIPPED_COUNT=0
# 使用 NULL 分隔符读取 commits
# -z: 使用 NULL 字符分隔输出
# %x00: 在格式中插入 NULL 字符
# 格式: 完整hash \0 作者 \0 主题 \0 正文 \0
while IFS= read -r -d '' hash && \
IFS= read -r -d '' author && \
IFS= read -r -d '' subject && \
IFS= read -r -d '' body; do
# 检查是否应该忽略此提交
SHOULD_SKIP=false
MATCHED_PATTERN=""
for pattern in "${IGNORE_PATTERNS[@]}"; do
# 跳过空模式
[ -z "$pattern" ] && continue
if [[ "$subject" =~ $pattern ]]; then
SHOULD_SKIP=true
MATCHED_PATTERN="$pattern"
break
fi
done
if [ "$SHOULD_SKIP" = true ]; then
echo " ⏭️ 跳过: ${subject:0:70}... (匹配: ${MATCHED_PATTERN})"
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
else
COMMITS_HASH+=("$hash")
COMMITS_AUTHOR+=("$author")
COMMITS_MESSAGE+=("$subject")
COMMITS_BODY+=("$body")
VALID_COUNT=$((VALID_COUNT + 1))
fi
done < <(git log $COMMIT_RANGE --no-merges --reverse -z --format="%H%x00%an%x00%s%x00%b%x00")
echo ""
echo "📊 提交统计:"
echo " ✓ 有效提交: $VALID_COUNT"
echo " ⏭️ 已跳过: $SKIPPED_COUNT"
echo " 📝 总计: $((VALID_COUNT + SKIPPED_COUNT))"
echo ""
if [ $VALID_COUNT -eq 0 ]; then
echo "⚠️ 未找到有效的提交"
echo "changelog_updated=false" >> $GITHUB_OUTPUT
echo "content_changed=false" >> $GITHUB_OUTPUT
exit 0
fi fi
TOTAL_COMMITS=$(wc -l < /tmp/commits.txt) # ============================================
echo "✓ 找到 $TOTAL_COMMITS 个提交" # 构建 CHANGELOG 条目
# ============================================
echo "📄 构建 CHANGELOG 条目..."
# Python 脚本生成 CHANGELOG REPO_URL="${{ github.server_url }}/${{ github.repository }}"
python3 << 'PYSCRIPT'
import re
import json
import sys
from datetime import datetime
# 读取配置 # 初始化条目内容
changelog_version = "${{ env.CHANGELOG_VERSION }}" NEW_ENTRY="## :bookmark: ${CHANGELOG_VERSION}\n\n"
gitea_server = "${{ env.GITEA_SERVER }}" NEW_ENTRY="${NEW_ENTRY}${CHANGELOG_SECTION_TITLE}\n\n"
repo = "${{ github.repository }}"
section_title = "${{ env.CHANGELOG_SECTION_TITLE }}"
contributors_title = "${{ env.CHANGELOG_CONTRIBUTORS_TITLE }}"
# 读取忽略模式 # 处理每个提交
ignore_patterns = json.loads('${{ env.IGNORE_PATTERNS_JSON }}') for ((i=0; i<$VALID_COUNT; i++)); do
print(f"🔍 Loaded {len(ignore_patterns)} ignore patterns:") hash="${COMMITS_HASH[$i]}"
for pattern in ignore_patterns: author="${COMMITS_AUTHOR[$i]}"
print(f" - {pattern}") message="${COMMITS_MESSAGE[$i]}"
print() body="${COMMITS_BODY[$i]}"
# 读取提交 echo " [$((i+1))/$VALID_COUNT] ${hash:0:7}: ${message:0:60}..."
with open('/tmp/commits.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
commits = [] # 构建提交链接
skipped_commits = [] short_hash="${hash:0:7}"
for line in lines: COMMIT_LINK="([${short_hash}](${REPO_URL}/commit/${hash}))"
line = line.strip()
if not line or '|' not in line:
continue
commit_hash, message = line.split('|', 1) # 处理 body (如果存在)
if [ -n "$body" ]; then
# 移除首尾空行,保留中间的空行
cleaned_body=$(echo "$body" | sed -e :a -e '/^\s*$/d;')
# 检查是否应该忽略 if [ -n "$cleaned_body" ]; then
should_ignore = False # 有 body: 主题换行后接 body然后是链接和作者
matched_pattern = None # Markdown 需要两个空格来实现换行
for pattern in ignore_patterns: NEW_ENTRY="${NEW_ENTRY}- ${message} \n"
if re.search(pattern, message):
should_ignore = True
matched_pattern = pattern
break
if should_ignore: # 将 body 按行添加,每行缩进两个空格
skipped_commits.append((message, matched_pattern)) while IFS= read -r line; do
else: NEW_ENTRY="${NEW_ENTRY} ${line} \n"
commits.append({'hash': commit_hash, 'message': message}) done <<< "$cleaned_body"
# 输出统计 # 在最后一行添加链接和作者
print(f"📊 Commit Statistics:") NEW_ENTRY="${NEW_ENTRY} ${COMMIT_LINK}"
print(f" Total: {len(lines)}") if [ -n "$author" ]; then
print(f" Valid: {len(commits)}") NEW_ENTRY="${NEW_ENTRY} by @${author}"
print(f" Skipped: {len(skipped_commits)}") fi
print() NEW_ENTRY="${NEW_ENTRY}\n"
else
# body 为空 (只有空白): 主题 + 链接 + 作者
NEW_ENTRY="${NEW_ENTRY}- ${message} ${COMMIT_LINK}"
if [ -n "$author" ]; then
NEW_ENTRY="${NEW_ENTRY} by @${author}"
fi
NEW_ENTRY="${NEW_ENTRY}\n"
fi
else
# 没有 body: 主题 + 链接 + 作者
NEW_ENTRY="${NEW_ENTRY}- ${message} ${COMMIT_LINK}"
if [ -n "$author" ]; then
NEW_ENTRY="${NEW_ENTRY} by @${author}"
fi
NEW_ENTRY="${NEW_ENTRY}\n"
fi
done
if skipped_commits: echo ""
print("⏭️ Skipped Commits:") echo "👥 收集贡献者..."
for msg, pattern in skipped_commits:
print(f" ⏭️ Skipping: {msg[:80]}... (matched: {pattern})")
print()
if not commits: # 收集贡献者 (去除 bot)
print("⚠️ No valid commits found") if [ "$COMMIT_RANGE" = "HEAD" ]; then
with open('/tmp/changelog_updated.txt', 'w') as f: CONTRIBUTORS=$(git log --pretty=format:"%an" --no-merges | \
f.write('false') grep -v "github-actions\[bot\]" | \
sys.exit(0) grep -v "dependabot\[bot\]" | \
grep -v "renovate\[bot\]" | \
sort -u)
else
CONTRIBUTORS=$(git log $COMMIT_RANGE --pretty=format:"%an" --no-merges | \
grep -v "github-actions\[bot\]" | \
grep -v "dependabot\[bot\]" | \
grep -v "renovate\[bot\]" | \
sort -u)
fi
# 收集贡献者 if [ -n "$CONTRIBUTORS" ]; then
contributors = set() CONTRIBUTOR_COUNT=$(echo "$CONTRIBUTORS" | wc -l)
for commit in commits: echo "✓ 找到 ${CONTRIBUTOR_COUNT} 个贡献者"
# 从提交消息中提取可能的用户名(如果使用了 @username 格式)
# 这里简化处理,实际可以通过 git log --format='%an|%ae' 获取
pass
# 生成 CHANGELOG 内容 NEW_ENTRY="${NEW_ENTRY}\n${{ env.CHANGELOG_CONTRIBUTORS_TITLE }}\n\n"
changelog_content = f"## :bookmark: {changelog_version}\n\n"
changelog_content += f"*Released on {datetime.now().strftime('%Y-%m-%d')}*\n\n"
changelog_content += f"{section_title}\n\n"
for commit in commits: while IFS= read -r name; do
short_hash = commit['hash'][:7] [ -z "$name" ] && continue
message = commit['message'] NEW_ENTRY="${NEW_ENTRY}<a href=\"${{ env.GITEA_SERVER }}/${name}\">\n"
changelog_content += f"- {message} ([`{short_hash}`](https://github.com/{repo}/commit/{commit['hash']}))\n" NEW_ENTRY="${NEW_ENTRY} <img src=\"${{ env.GITEA_SERVER }}/${name}.png\" alt=\"${name}\" width=\"35\" height=\"35\" style=\"border-radius: 50%;\" onerror=\"this.src='${{ env.GITEA_SERVER }}/assets/img/avatar_default.png'\" />\n"
NEW_ENTRY="${NEW_ENTRY}</a>\n"
done <<< "$CONTRIBUTORS"
fi
# 如果有贡献者信息,可以在这里添加 NEW_ENTRY="${NEW_ENTRY}\n---\n"
# changelog_content += f"\n{contributors_title}\n\n"
changelog_content += "\n---\n\n" echo ""
echo "💾 写入 CHANGELOG.md..."
# 切换到主分支
git checkout ${{ env.MAIN_BRANCH }}
# ============================================
# 更新或创建 CHANGELOG.md # 更新或创建 CHANGELOG.md
changelog_file = 'CHANGELOG.md' # ============================================
try:
with open(changelog_file, 'r', encoding='utf-8') as f:
existing_content = f.read()
except FileNotFoundError:
existing_content = "# :memo: CHANGELOG\n\n"
# 检查版本是否已存在 if [ -f CHANGELOG.md ]; then
if f"## :bookmark: {changelog_version}" in existing_content: # CHANGELOG 文件已存在
print(f" Version {changelog_version} already exists, appending commits") if grep -q "^## :bookmark: ${CHANGELOG_VERSION}$" CHANGELOG.md; then
# 找到版本区块,在其中追加新的提交 # 版本已存在 - 内容叠加模式
pattern = rf'(## :bookmark: {re.escape(changelog_version)}.*?\n{re.escape(section_title)}\n\n)(.*?)(\n---|\n## :bookmark: |\Z)' echo "🔄 版本 ${CHANGELOG_VERSION} 已存在,执行内容叠加..."
match = re.search(pattern, existing_content, re.DOTALL)
if match: # 提取现有版本区域中的 commit hashes (用于去重)
existing_commits = match.group(2).strip() EXISTING_HASHES=$(awk '
new_commits = changelog_content.split(f"{section_title}\n\n")[1].split("\n---")[0].strip() BEGIN { in_version=0 }
/^## :bookmark: '"${CHANGELOG_VERSION}"'$/ { in_version=1; next }
/^## :bookmark: / && in_version { in_version=0 }
in_version && /\(\[([0-9a-f]{7})\]/ {
match($0, /\(\[([0-9a-f]{7})\]/, arr)
print arr[1]
}
' CHANGELOG.md | sort -u)
# 合并提交,去重 # 找出真正的新 commits (基于 hash 去重)
all_commits_text = existing_commits + "\n" + new_commits NEW_COMMIT_ENTRIES=""
# 简单去重(基于完整行) NEW_COMMIT_COUNT=0
unique_lines = []
seen = set()
for line in all_commits_text.split('\n'):
if line.strip() and line not in seen:
unique_lines.append(line)
seen.add(line)
merged_commits = '\n'.join(unique_lines) for ((i=0; i<$VALID_COUNT; i++)); do
hash="${COMMITS_HASH[$i]}"
short_hash="${hash:0:7}"
# 重新组装版本区块 # 检查这个 commit 是否已存在
updated_section = match.group(1) + merged_commits + "\n\n---\n" if echo "$EXISTING_HASHES" | grep -q "^${short_hash}$"; then
new_content = existing_content[:match.start()] + updated_section + existing_content[match.end():] echo " 跳过已存在: ${short_hash} ${COMMITS_MESSAGE[$i]:0:50}..."
else: continue
print("⚠️ Could not find version section, prepending new content") fi
# 尝试匹配各种可能的 CHANGELOG 标题格式
header_pattern = r'^#\s*(?::[\w_]+:)?\s*CHANGELOG\s*\n'
match = re.search(header_pattern, existing_content, re.IGNORECASE | re.MULTILINE)
if match: # 这是一个新的 commit添加它
insert_pos = match.end() author="${COMMITS_AUTHOR[$i]}"
if not existing_content[insert_pos:insert_pos+1] == '\n': message="${COMMITS_MESSAGE[$i]}"
new_content = existing_content[:insert_pos] + '\n' + changelog_content + existing_content[insert_pos:] body="${COMMITS_BODY[$i]}"
else:
new_content = existing_content[:insert_pos] + '\n' + changelog_content + existing_content[insert_pos+1:]
else:
# 没有找到标题,在开头插入
new_content = f"# :memo: CHANGELOG\n\n{changelog_content}{existing_content}"
else:
# 插入新版本
# 尝试匹配各种可能的 CHANGELOG 标题格式
# 支持: # CHANGELOG, # :memo: CHANGELOG, # Changelog 等
# 尝试找到 CHANGELOG 标题(不区分大小写,可选 emoji COMMIT_LINK="([${short_hash}](${REPO_URL}/commit/${hash}))"
header_pattern = r'^#\s*(?::[\w_]+:)?\s*CHANGELOG\s*\n'
match = re.search(header_pattern, existing_content, re.IGNORECASE | re.MULTILINE)
if match: if [ -n "$body" ]; then
# 找到了 CHANGELOG 标题,在其后插入 cleaned_body=$(echo "$body" | sed -e :a -e '/^\s*$/d;')
insert_pos = match.end() if [ -n "$cleaned_body" ]; then
# 确保标题后有空行 NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES}- ${message} \n"
if not existing_content[insert_pos:insert_pos+1] == '\n': while IFS= read -r line; do
new_content = existing_content[:insert_pos] + '\n' + changelog_content + existing_content[insert_pos:] NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES} ${line} \n"
else: done <<< "$cleaned_body"
new_content = existing_content[:insert_pos] + '\n' + changelog_content + existing_content[insert_pos+1:] NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES} ${COMMIT_LINK}"
else: else
# 没有找到 CHANGELOG 标题,在文件开头插入标题和内容 NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES}- ${message} ${COMMIT_LINK}"
if existing_content.strip(): fi
# 文件有内容但没有 CHANGELOG 标题 else
new_content = f"# :memo: CHANGELOG\n\n{changelog_content}{existing_content}" NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES}- ${message} ${COMMIT_LINK}"
else: fi
# 文件为空
new_content = f"# :memo: CHANGELOG\n\n{changelog_content}"
# 检查内容是否真的改变 if [ -n "$author" ]; then
content_changed = (new_content != existing_content) NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES} by @${author}"
version_exists = f"## :bookmark: {changelog_version}" in existing_content fi
if content_changed: NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES}\n"
with open(changelog_file, 'w', encoding='utf-8') as f: NEW_COMMIT_COUNT=$((NEW_COMMIT_COUNT + 1))
f.write(new_content) done
print(f"✅ CHANGELOG updated successfully") if [ $NEW_COMMIT_COUNT -gt 0 ]; then
print(f" Added {len(commits)} commits to version {changelog_version}") echo "✓ 发现 ${NEW_COMMIT_COUNT} 个新的commits需要添加"
with open('/tmp/changelog_updated.txt', 'w') as f: # 在 Contributors 部分之前插入新的 commits
f.write('true') TEMP_FILE=$(mktemp)
with open('/tmp/content_changed.txt', 'w') as f: IN_VERSION=false
f.write('true') ADDED=false
elif version_exists:
# 内容没有变化,但版本已存在
# 这通常发生在重建已删除的 tag
# 我们仍然应该创建 Release从现有 CHANGELOG 提取内容)
print(f" No new content to add")
print(f" Version {changelog_version} already contains all {len(commits)} commits")
print(f" This usually happens when recreating a deleted tag")
print(f" Will still create Release from existing CHANGELOG content")
with open('/tmp/changelog_updated.txt', 'w') as f: while IFS= read -r line; do
f.write('true') # 返回 true 以触发 Release 创建 # 检测版本标题
with open('/tmp/content_changed.txt', 'w') as f: if [[ "$line" =~ ^##[[:space:]]:bookmark:[[:space:]]${CHANGELOG_VERSION}$ ]]; then
f.write('false') # 但文件没有变化,不需要提交 echo "$line" >> "$TEMP_FILE"
else: IN_VERSION=true
# 既没有新内容,版本也不存在(不应该发生) # 检测到 Contributors 标题,在它之前插入新 commits
print(f"⚠️ Unexpected state: no content change and version doesn't exist") elif [[ "$line" =~ ^###[[:space:]]:busts_in_silhouette: ]] && [ "$IN_VERSION" = true ] && [ "$ADDED" = false ]; then
echo "" >> "$TEMP_FILE"
echo -e "${NEW_COMMIT_ENTRIES}" >> "$TEMP_FILE"
echo "$line" >> "$TEMP_FILE"
ADDED=true
# 遇到下一个版本标题
elif [[ "$line" =~ ^##[[:space:]]:bookmark: ]] && [ "$IN_VERSION" = true ]; then
# 如果还没添加(可能没有 Contributors 部分)
if [ "$ADDED" = false ]; then
echo "" >> "$TEMP_FILE"
echo -e "${NEW_COMMIT_ENTRIES}" >> "$TEMP_FILE"
ADDED=true
fi
echo "$line" >> "$TEMP_FILE"
IN_VERSION=false
else
echo "$line" >> "$TEMP_FILE"
fi
done < CHANGELOG.md
with open('/tmp/changelog_updated.txt', 'w') as f: # 如果到文件末尾还没添加(版本在最后且没有 Contributors
f.write('false') if [ "$IN_VERSION" = true ] && [ "$ADDED" = false ]; then
with open('/tmp/content_changed.txt', 'w') as f: echo "" >> "$TEMP_FILE"
f.write('false') echo -e "${NEW_COMMIT_ENTRIES}" >> "$TEMP_FILE"
PYSCRIPT fi
UPDATED=$(cat /tmp/changelog_updated.txt) mv "$TEMP_FILE" CHANGELOG.md
CONTENT_CHANGED=$(cat /tmp/content_changed.txt 2>/dev/null || echo "false") echo "✅ 已追加 ${NEW_COMMIT_COUNT} 个新commits到现有版本"
echo "changelog_updated=$UPDATED" >> $GITHUB_OUTPUT echo "changelog_updated=true" >> $GITHUB_OUTPUT
echo "content_changed=$CONTENT_CHANGED" >> $GITHUB_OUTPUT echo "content_changed=true" >> $GITHUB_OUTPUT
else
echo " 所有commits都已存在于CHANGELOG中"
echo " (通常发生在重建已删除的 tag)"
echo " 将从现有内容创建 Release"
if [ "$UPDATED" = "true" ]; then echo "changelog_updated=true" >> $GITHUB_OUTPUT
echo "" echo "content_changed=false" >> $GITHUB_OUTPUT
echo "✅ CHANGELOG 生成完成" fi
else
# 版本不存在 - 添加新版本条目
echo " 添加新版本条目: ${CHANGELOG_VERSION}"
TEMP_FILE=$(mktemp)
# 读取第一行 (CHANGELOG 标题)
head -n 1 CHANGELOG.md > "$TEMP_FILE"
echo "" >> "$TEMP_FILE"
# 插入新条目
echo -e "${NEW_ENTRY}" >> "$TEMP_FILE"
# 追加剩余内容 (从第3行开始跳过标题后的空行)
tail -n +3 CHANGELOG.md >> "$TEMP_FILE"
mv "$TEMP_FILE" CHANGELOG.md
echo "✅ 已添加新的CHANGELOG条目"
echo "changelog_updated=true" >> $GITHUB_OUTPUT
echo "content_changed=true" >> $GITHUB_OUTPUT
fi
else else
echo "" # CHANGELOG 文件不存在 - 创建新文件
echo " 没有有效的提交,跳过 CHANGELOG 更新" echo "📄 创建新的 CHANGELOG.md"
echo "# :memo: CHANGELOG" > CHANGELOG.md
echo "" >> CHANGELOG.md
echo -e "${NEW_ENTRY}" >> CHANGELOG.md
echo "✅ 已创建新的CHANGELOG.md"
echo "changelog_updated=true" >> $GITHUB_OUTPUT
echo "content_changed=true" >> $GITHUB_OUTPUT
fi fi
echo ""
echo "✅ CHANGELOG 生成完成"
echo "======================================" echo "======================================"
echo "" echo ""
@ -828,10 +936,6 @@ jobs:
id: upload_assets id: upload_assets
if: steps.changelog.outputs.changelog_updated == 'true' if: steps.changelog.outputs.changelog_updated == 'true'
run: | run: |
echo "======================================"
echo "📎 上传 Release 附件"
echo "======================================"
cd ${{ env.REPO_DIR }} cd ${{ env.REPO_DIR }}
echo "🔍 提取 Release ID..." echo "🔍 提取 Release ID..."