@@ -5,6 +5,10 @@ on:
tags :
- "[0-9]*"
concurrency :
group : release-${{ github.repository }}
cancel-in-progress : false
# ==========================================
# 🔧 配置区域 - 根据你的项目修改
# ==========================================
@@ -33,7 +37,7 @@ env:
# ===== 分支配置 =====
# 主分支名称(用于推送 CHANGELOG 更新)
# 如果留空,会自动检测(main 或 master)
MAIN_BRANCH : ""
MAIN_BRANCH : "main "
# ===== 服务器配置 =====
# Gitea 服务器地址(用于生成头像链接和 API 调用)
@@ -63,6 +67,8 @@ env:
# - "true": 创建为草稿,不会立即发布
# - "false": 立即发布 Release
RELEASE_IS_DRAFT : "false"
# 统一管理 pre-release 关键词
PRERELEASE_KEYWORDS : "alpha|beta|rc|pre|preview|dev|test"
# 额外要上传到 Release 的文件(空格分隔)
# 例如: "README.md LICENSE docs/guide.pdf"
ADDITIONAL_RELEASE_FILES : ""
@@ -137,10 +143,14 @@ jobs:
# 从环境变量读取配置
VERSION="${{ github.ref_name }}"
KEYWORDS="${{ env.PRERELEASE_KEYWORDS }}"
STRIP_REGEX="-($KEYWORDS)[0-9]*$" # 用于 sed 删除后缀
MATCH_REGEX="($KEYWORDS)" # 用于 bash =~ 匹配
# 处理 CHANGELOG 版本号
if [[ "${{ env.CHANGELOG_VERSION_MODE }}" == "strip" ]]; then
# 去除 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 " Tag: $VERSION → CHANGELOG: $CHANGELOG_VERSION"
else
@@ -160,7 +170,7 @@ jobs:
# 智能判断 pre-release
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"
PRERELEASE_DETECTED="yes (auto-detected: $VERSION contains pre-release keyword)"
else
@@ -172,12 +182,6 @@ jobs:
PRERELEASE_DETECTED="${{ env.RELEASE_PRERELEASE_MODE }} (manually configured)"
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 }}"
IGNORE_JSON="["
@@ -189,6 +193,16 @@ jobs:
fi
done
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
# 显示配置摘要
@@ -259,6 +273,13 @@ jobs:
if [ $? -ne 0 ]; then
echo "❌ 克隆失败"
cat /tmp/git_clone.log
# 清理残留
if [ -d "$REPO_DIR" ]; then
rm -rf "$REPO_DIR"
fi
exit 1
fi
@@ -283,26 +304,37 @@ jobs:
fi
fi
# 检测主分支名称
echo ""
echo "🔍 检测 主分支名称 ..."
if [ -n "${{ env.MAIN_BRANCH }}" ]; then
MAIN_BRANCH="${{ env.MAIN_BRANCH }}"
echo "✓ 使用配置的主分支: $MAIN_BRANCH"
else
# 自动检测 main 或 master
if git show-ref --verify --quiet refs/remotes/origin/main; then
MAIN_BRANCH="main "
echo "✓ 自动检测到主分支: main "
elif git show-ref --verify --quiet refs/remotes/origin/master; then
MAIN_BRANCH="master "
echo "✓ 自动检测到主分支: master"
else
echo "❌ 错误: 无法检测主分支(main 或 master) "
echo "🔍 验证 主分支配置 ..."
# 检查是否配置了主分支
if [ -z "${{ env.MAIN_BRANCH }}" ]; then
echo "❌ 错误: 未配置主分支"
echo ""
echo "请在 workflow 配置文件的 env 区域设置 MAIN_BRANCH:"
echo " "
echo "env: "
echo " MAIN_BRANCH: \"main\" # 或 \"master\""
echo " "
exit 1
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 "🌿 切换到主分支: $MAIN_BRANCH"
@@ -346,38 +378,9 @@ jobs:
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
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 : |
echo "======================================"
echo "📝 生成 CHANGELOG"
@@ -407,7 +410,7 @@ jobs:
# 对每个 tag 执行 strip 操作(去除 pre-release 后缀)
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
tag_stripped="$tag"
fi
@@ -420,248 +423,398 @@ jobs:
done <<< "$ALL_TAGS"
if [ -z "$PREVIOUS_TAG" ]; then
echo "ℹ ️ No previous tag found, using all commits "
COMMIT_RANGE=""
echo "ℹ ️ 未找到前一个tag,将使用所有提交 "
COMMIT_RANGE="HEAD "
else
echo "📍 Previous tag: $PREVIOUS_TAG"
echo "📍 前一个 tag: $PREVIOUS_TAG"
# 对 previous tag 也执行 strip,显示对应的 CHANGELOG 版本
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)"
fi
COMMIT_RANGE="${PREVIOUS_TAG}..${VERSION}"
fi
# 获取提交记录
echo ""
echo "📋 获取 提交记录..."
if [ -z "$COMMIT_RANGE" ]; then
git log --pretty=format:'%H|%s' > /tmp/commits.txt
else
git log --pretty=format:'%H|%s' "$COMMIT_RANGE" > /tmp/commits.txt
echo "🔍 扫描 提交记录..."
TEMP_COMMITS=$(mktemp)
git log $COMMIT_RANGE --no-merges --reverse --format="COMMIT_START%n%H%n%an%n%s%nBODY_START%n%b%nBODY_END" > "$TEMP_COMMITS"
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[@]} 个忽略模式"
COMMIT_INDEX=0
CURRENT_HASH=""
CURRENT_AUTHOR=""
CURRENT_MESSAGE=""
CURRENT_BODY=""
IN_BODY=false
SKIPPED_COUNT=0
while IFS= read -r line; do
if [[ "$line" == "COMMIT_START" ]]; then
# 处理前一个 commit
if [ -n "$CURRENT_HASH" ]; then
SHOULD_SKIP=false
for pattern in "${IGNORE_PATTERNS[@]}"; do
[ -z "$pattern" ] && continue
if echo "$CURRENT_MESSAGE" | grep -qiE "$pattern"; then
SHOULD_SKIP=true
echo " ⏭️ 跳过: ${CURRENT_MESSAGE:0:70}... (匹配: ${pattern})"
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
break
fi
done
if [ "$SHOULD_SKIP" = false ]; then
COMMITS_HASH[$COMMIT_INDEX]="$CURRENT_HASH"
COMMITS_AUTHOR[$COMMIT_INDEX]="$CURRENT_AUTHOR"
COMMITS_MESSAGE[$COMMIT_INDEX]="$CURRENT_MESSAGE"
COMMITS_BODY[$COMMIT_INDEX]="$CURRENT_BODY"
COMMIT_INDEX=$((COMMIT_INDEX + 1))
fi
fi
TOTAL_COMMITS=$(wc -l < /tmp/commits.txt)
echo "✓ 找到 $TOTAL_COMMITS 个提交 "
# 重置状态
CURRENT_HASH=" "
CURRENT_AUTHOR=""
CURRENT_MESSAGE=""
CURRENT_BODY=""
IN_BODY=false
STATE="HASH"
# Python 脚本生成 CHANGELOG
python3 << 'PYSCRIPT'
import re
import jso n
import sys
from datetime import datetime
elif [[ "$line" == "BODY_START" ]]; then
IN_BODY=true
CURRENT_BODY=""
elif [[ "$line" == "BODY_END" ]]; the n
IN_BODY=false
elif [ "$IN_BODY" = true ]; then
if [ -n "$CURRENT_BODY" ]; then
CURRENT_BODY="${CURRENT_BODY}"$'\n'"${line}"
else
CURRENT_BODY="$line"
fi
else
if [ -z "$CURRENT_HASH" ]; then
CURRENT_HASH="$line"
elif [ -z "$CURRENT_AUTHOR" ]; then
CURRENT_AUTHOR="$line"
elif [ -z "$CURRENT_MESSAGE" ]; then
CURRENT_MESSAGE="$line"
fi
fi
done < "$TEMP_COMMITS"
# 读取配置
changelog_version = "${{ env.CHANGELOG_VERSION }}"
gitea_server = "${{ env.GITEA_SERVER }}"
repo = "${{ github.repository }}"
secti on_ title = "${{ env.CHANGELOG_SECTION_TITLE }}"
contributors_title = "${{ env.CHANGELOG_CONTRIBUTORS_TITLE }}"
# 读取忽略模式
ignore_patterns = json.loads('${{ env.IGNORE_PATTERNS_JSON }}' )
print(f"🔍 Loaded {len(ignore_patterns)} ignore patterns:")
for pattern in ignore_patterns:
print(f" - {pattern}")
print()
# 读取提交
with open('/tmp/commits.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
commits = []
skipped_commits = []
for line in lines:
line = line.strip()
if not line or '|' not in line:
continue
commit_hash, message = line.split('|', 1)
# 检查是否应该忽略
should_ignore = False
matched_pattern = None
for pattern in ignore_patterns:
if re.search(pattern, message):
should_ignore = True
matched_pattern = pattern
# 处理最后一个 commit
if [ -n "$CURRENT_HASH" ]; then
SHOULD_SKIP=false
for pattern in "${IGNORE_PATTERNS[@]}"; do
[ -z "$pattern" ] && c ontinue
if echo "$CURRENT_MESSAGE" | grep -qiE "$pattern"; then
SHOULD_SKIP=true
echo " ⏭️ 跳过: ${CURRENT_MESSAGE:0:70}... (匹配: ${pattern})"
SKIPPED_COUNT=$((SKIPPED_COUNT + 1) )
break
fi
done
if should_ignore:
skipped_commits.append((message, matched_pattern))
else:
commits.append({'hash': commit_hash, 'message': message})
if [ "$SHOULD_SKIP" = false ]; then
COMMITS_HASH[$COMMIT_INDEX]="$CURRENT_HASH"
COMMITS_AUTHOR[$COMMIT_INDEX]="$CURRENT_AUTHOR"
COMMITS_MESSAGE[$COMMIT_INDEX]="$CURRENT_MESSAGE"
COMMITS_BODY[$COMMIT_INDEX]="$CURRENT_BODY"
COMMIT_INDEX=$((COMMIT_INDEX + 1))
fi
fi
# 输出统计
print(f"📊 Commit Statistics:")
print(f" Total: {len(lines)}")
print(f" Valid: {len(commits)}")
print(f" Skipped: {len(skipped_commits)}")
print()
rm -f "$TEMP_COMMITS"
if skipped_commits:
print("⏭️ Skipped Commits:")
for msg, pattern in skipped_commits:
print(f" ⏭️ Skipping: {msg[:80]}... (matched: {pattern})")
print()
VALID_COUNT=$COMMIT_INDEX
if not commits:
print("⚠️ No valid commits found")
with open('/tmp/changelog_updated.txt', 'w') as f:
f.write('false')
sys.exit(0)
echo ""
echo "📊 提交统计:"
echo " ✓ 有效提交: $VALID_COUNT"
echo " ⏭️ 已跳过: $SKIPPED_COUNT"
echo " 📝 总计: $((VALID_COUNT + SKIPPED_COUNT))"
echo ""
# 收集贡献者
contributors = set()
for commit in commits:
# 从提交消息中提取可能的用户名(如果使用了 @username 格式)
# 这里简化处理,实际可以通过 git log --format='%an|%ae' 获取
pass
if [ $VALID_COUNT -eq 0 ]; then
echo "⚠️ 未找到有效的提交"
echo "changelog_updated=false" >> $GITHUB_OUTPUT
echo "content_changed=false" >> $GITHUB_OUTPUT
exit 0
fi
# 生成 CHANGELOG 内容
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 "
# ============================================
# 构建 CHANGELOG 条目
# ============================================
e cho "📄 构建 CHANGELOG 条目... "
for commit in commits:
short_hash = commit['hash'][:7]
message = commit['message']
changelog_content += f"- {message} ([`{short_hash}`](https://github.com/{repo}/commit/{commit['hash']}))\n"
REPO_URL="${{ github.server_url }}/${{ github.repository }}"
# 如果有贡献者信息,可以在这里添加
# changelog_content += f"\n{contributors_title }\n\n"
# 初始化条目内容
NEW_ENTRY="## :bookmark: ${CHANGELOG_VERSION }\n\n"
NEW_ENTRY="${NEW_ENTRY}${CHANGELOG_SECTION_TITLE}\n\n"
changelog_content += "\n---\n\n"
# 处理每个提交
for ((i=0; i<$VALID_COUNT; i++)); do
hash="${COMMITS_HASH[$i]}"
author="${COMMITS_AUTHOR[$i]}"
message="${COMMITS_MESSAGE[$i]}"
body="${COMMITS_BODY[$i]}"
echo " [$((i+1))/$VALID_COUNT] ${hash:0:7}: ${message:0:60}..."
# 构建提交链接 - 使用短 hash 格式
short_hash="${hash:0:7}"
COMMIT_LINK="([${short_hash}](${REPO_URL}/commit/${hash}))"
# 构建作者信息
AUTHOR_INFO=""
if [ -n "$author" ]; then
AUTHOR_INFO=" by @${author}"
fi
# 处理 body (如果存在)
if [ -n "$body" ]; then
# 移除首尾空行
cleaned_body=$(echo "$body" | sed -e :a -e '/^\s*$/d;')
if [ -n "$cleaned_body" ]; then
# 有 body: 主题在第一行,body 内容缩进显示,链接和作者放在一起
NEW_ENTRY="${NEW_ENTRY}- ${message} \n"
# 将 body 按行添加,每行缩进两个空格,使用 Markdown 换行
while IFS= read -r line; do
[ -z "$line" ] && continue
NEW_ENTRY="${NEW_ENTRY} ${line} \n"
done <<< "$cleaned_body"
# 链接和作者放在缩进行
NEW_ENTRY="${NEW_ENTRY} ${COMMIT_LINK}${AUTHOR_INFO}\n"
else
# body 为空 (只有空白): 单行格式
NEW_ENTRY="${NEW_ENTRY}- ${message} ${COMMIT_LINK}${AUTHOR_INFO}\n"
fi
else
# 没有 body: 单行格式
NEW_ENTRY="${NEW_ENTRY}- ${message} ${COMMIT_LINK}${AUTHOR_INFO}\n"
fi
done
echo ""
echo "👥 收集贡献者..."
# 收集贡献者 (去除 bot)
if [ "$COMMIT_RANGE" = "HEAD" ]; then
CONTRIBUTORS=$(git log --pretty=format:"%an" --no-merges | \
grep -v "github-actions\[bot\]" | \
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
CONTRIBUTOR_COUNT=$(echo "$CONTRIBUTORS" | wc -l)
echo "✓ 找到 ${CONTRIBUTOR_COUNT} 个贡献者"
NEW_ENTRY="${NEW_ENTRY}\n${{ env.CHANGELOG_CONTRIBUTORS_TITLE }}\n\n"
while IFS= read -r name; do
[ -z "$name" ] && continue
NEW_ENTRY="${NEW_ENTRY}<a href=\"${{ env.GITEA_SERVER }}/${name}\">\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"
echo ""
echo "💾 写入 CHANGELOG.md..."
# 切换到主分支
git checkout ${{ env.MAIN_BRANCH }}
# ============================================
# 更新或创建 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"## :bookmark: {changelog_version}" in existing_content:
print(f"ℹ ️ Version {changelog_version} already exists, appending commits")
# 找到版本区块,在其中追加新的提交
pattern = rf'(## :bookmark: {re.escape(changelog_version)}.*?\n{re.escape(section_title)}\n\n)(.*?)(\n---|\n## :bookmark: |\Z)'
match = re.search(pattern, existing_content, re.DOTALL)
if [ -f CHANGELOG.md ]; then
# CHANGELOG 文件已存在
if grep -q "^## :bookmark: ${CHANGELOG_VERSION}$" CHANGELOG.md; then
# 版本已存在 - 内容叠加模式
echo "🔄 版本 ${CHANGELOG_VERSION} 已存在,执行内容叠加..."
if match:
existing_commits = match.group(2).strip()
new_commits = changelog_content.split(f"{section_title}\n\n")[1].split("\n---")[0].strip()
# 提取现有版本区域中的 commit hashes (用于去重)
EXISTING_HASHES=$(awk '
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)
# 合并提交, 去重
all_commits_text = existing_commits + "\n" + new_commits
# 简单去重(基于完整行)
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)
# 找出真正的新 commits (基于 hash 去重)
NEW_COMMIT_ENTRIES=""
NEW_COMMIT_COUNT=0
merged_commits = '\n'.join(unique_lines)
for ((i=0; i<$VALID_COUNT; i++)); do
hash="${COMMITS_HASH[$i]}"
short_hash="${hash:0:7}"
# 重新组装版本区块
updated_section = match.group(1) + merged_commits + "\n\n---\n"
new_content = existing_content[:match.start()] + updated_section + existing_content[match.end():]
else:
print("⚠️ Could not find version section, prepending new content")
# 尝试匹配各种可能的 CHANGELOG 标题格式
header_pattern = r'^#\s*(?::[\w_]+:)?\s*CHANGELOG\s*\n'
match = re.search(header_pattern, existing_content, re.IGNORECASE | re.MULTILINE)
# 检查这个 commit 是否已存在
if echo "$EXISTING_HASHES" | grep -q "^${short_hash}$"; then
echo " ℹ ️ 跳过已存在: ${short_hash} ${COMMITS_MESSAGE[$i]:0:50}..."
continue
fi
if match:
insert_pos = match.end()
if not existing_content[insert_pos:insert_pos+1] == '\n':
new_content = existing_content[:insert_pos] + '\n' + changelog_content + existing_content[insert_pos:]
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 等
# 这是一个新的 commit,添加它
author="${COMMITS_AUTHOR[$i]}"
message="${COMMITS_MESSAGE[$i]}"
body="${COMMITS_BODY[$i]}"
# 尝试找到 CHANGELOG 标题(不区分大小写,可选 emoji)
header_pattern = r'^#\s*(?::[\w_]+:)?\s*CHANGELOG\s*\n'
match = re.search(header_pattern, existing_content, re.IGNORECASE | re.MULTILINE)
COMMIT_LINK="([${short_hash}](${REPO_URL}/commit/${hash}))"
if match:
# 找到了 CHANGELOG 标题,在其后插入
insert_pos = match.end()
# 确保标题后有空行
if not existing_content[insert_pos:insert_pos+1] == '\n':
new_content = existing_content[:insert_pos] + '\n' + changelog_content + existing_content[insert_pos:]
else:
new_content = existing_content[:insert_pos] + '\n' + changelog_content + existing_content[insert_pos+1:]
else:
# 没有找到 CHANGELOG 标题,在文件开头插入标题和内容
if existing_content.strip():
# 文件有内容但没有 CHANGELOG 标题
new_content = f"# :memo: CHANGELOG\n\n{changelog_content}{existing_content}"
else:
# 文件为空
new_content = f"# :memo: CHANGELOG\n\n{changelog_content}"
# 构建作者信息
AUTHOR_INFO=""
if [ -n "$author" ]; then
AUTHOR_INFO=" by @${author}"
fi
# 检查内容是否真的改变
content_ch ang ed = (new_content != existing_content )
version_exists = f"## :bookmark: {changelog_version}" in existing_cont ent
if [ -n "$body" ]; then
cle aned_body=$(echo "$body" | sed -e :a -e '/^\s*$/d;' )
if [ -n "$cleaned_body" ]; th en
NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES}- ${message} \n"
while IFS= read -r line; do
[ -z "$line" ] && continue
NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES} ${line} \n"
done <<< "$cleaned_body"
NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES} ${COMMIT_LINK}${AUTHOR_INFO}\n"
else
NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES}- ${message} ${COMMIT_LINK}${AUTHOR_INFO}\n"
fi
else
NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES}- ${message} ${COMMIT_LINK}${AUTHOR_INFO}\n"
fi
if content_changed:
with open(changelog_file, 'w', encoding='utf-8') as f:
f.write(new_content)
NEW_COMMIT_COUNT=$((NEW_COMMIT_COUNT + 1))
done
print(f"✅ CHANGELOG updated successfully")
print(f" Added {len(commits)} commits to version {changelog_version}")
if [ $NEW_COMMIT_COUNT -gt 0 ]; then
echo "✓ 发现 ${NEW_COMMIT_COUNT} 个新的commits需要添加"
with open('/tmp/changelog_updated.txt', 'w') as f:
f.write('true' )
with open('/tmp/content_changed.txt', 'w') as f:
f.write('true')
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")
# 在 Contributors 部分之前插入新的 commits
TEMP_FILE=$(mktemp )
IN_VERSION=false
ADDED=false
with open('/tmp/changelog_updated.txt', 'w') as f:
f.write('true') # 返回 true 以触发 Release 创建
with open('/tmp/content_changed.txt', 'w') as f:
f.write('false') # 但文件没有变化,不需要提交
else:
# 既没有新内容,版本也不存在(不应该发生)
print(f"⚠️ Unexpected state: no content change and version doesn't exist")
while IFS= read -r line; do
# 检测版本标题
if [[ "$line" =~ ^##[[:space:]]:bookmark:[[:space:]]${CHANGELOG_VERSION}$ ]]; then
echo "$line" >> "$TEMP_FILE"
IN_VERSION=true
# 检测到 Contributors 标题,在它之前插入新 commits
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:
f.write('false')
with open('/tmp/content_changed.txt', 'w') as f:
f.write('false')
PYSCRIPT
# 如果到文件末尾还没添加(版本在最后且没有 Contributors)
if [ "$IN_VERSION" = true ] && [ "$ADDED" = false ]; then
echo "" >> "$TEMP_FILE"
echo -e "${NEW_COMMIT_ENTRIES}" >> "$TEMP_FILE"
fi
UPDATED=$(cat /tmp/changelog_updated.txt)
CONTENT_CHANGED=$(cat /tmp/content_changed.txt 2>/dev/null || echo "false")
mv "$TEMP_FILE" CHANGELOG.md
echo "✅ 已追加 ${NEW_COMMIT_COUNT} 个新commits到现有版本"
echo "changelog_updated=$UPDATED " >> $GITHUB_OUTPUT
echo "content_changed=$CONTENT_CHANGED " >> $GITHUB_OUTPUT
echo "changelog_updated=true " >> $GITHUB_OUTPUT
echo "content_changed=true " >> $GITHUB_OUTPUT
else
echo "ℹ ️ 所有commits都已存在于CHANGELOG中"
echo " (通常发生在重建已删除的 tag)"
echo " 将从现有内容创建 Release"
echo "changelog_updated=true" >> $GITHUB_OUTPUT
echo "content_changed=false" >> $GITHUB_OUTPUT
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
# 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
if [ "$UPDATED" = "true" ]; then
echo ""
echo "✅ CHANGELOG 生成完成"
else
echo ""
echo "ℹ ️ 没有有效的提交,跳过 CHANGELOG 更新"
fi
echo "======================================"
echo ""
@@ -828,10 +981,6 @@ jobs:
id : upload_assets
if : steps.changelog.outputs.changelog_updated == 'true'
run : |
echo "======================================"
echo "📎 上传 Release 附件"
echo "======================================"
cd ${{ env.REPO_DIR }}
echo "🔍 提取 Release ID..."
@@ -862,19 +1011,6 @@ jobs:
echo "✓ Release ID: $RELEASE_ID"
echo ""
# 上传 CHANGELOG.md
echo "📤 上传 CHANGELOG.md..."
HTTP_CODE=$(curl -s -o /tmp/upload_response_changelog.json -w "%{http_code}" -X POST \
-H "Authorization: token ${{ env.ACCESS_TOKEN }}" \
-F "attachment=@CHANGELOG.md" \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/$RELEASE_ID/assets?name=CHANGELOG.md")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
echo "✅ CHANGELOG.md 上传成功"
else
echo "⚠️ 警告: CHANGELOG.md 上传失败 (HTTP $HTTP_CODE)"
fi
# 上传额外的文件
if [ -n "${{ env.ADDITIONAL_RELEASE_FILES }}" ]; then
echo ""