diff --git a/.gitea/workflows/changelog_and_release.yml b/.gitea/workflows/changelog_and_release.yml index aa26b9b..7d18b0c 100644 --- a/.gitea/workflows/changelog_and_release.yml +++ b/.gitea/workflows/changelog_and_release.yml @@ -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,17 +193,27 @@ 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 # 显示配置摘要 echo "" echo "📋 Configuration Summary:" - echo " 🏷️ Tag version: $VERSION" + echo " 🏷️ Tag version: $VERSION" echo " 📝 CHANGELOG version: $CHANGELOG_VERSION" echo " 🌐 Gitea Server: ${{ env.GITEA_SERVER }}" echo " 📄 CHANGELOG Title: ${{ env.CHANGELOG_SECTION_TITLE }}" echo " 🚀 Release Title: $RELEASE_TITLE_PROCESSED" - echo " 🏷️ Pre-release: $PRERELEASE_DETECTED" + echo " 🏷️ Pre-release: $PRERELEASE_DETECTED" echo " 📄 Draft: ${{ env.RELEASE_IS_DRAFT }}" echo " 💬 Commit Message: $COMMIT_MSG" echo " 📎 Additional Files: ${ADDITIONAL_RELEASE_FILES:-none}" @@ -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)" - exit 1 - fi + 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,353 @@ 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 "🔍 扫描提交记录..." + + # ============================================ + # 使用 NULL 分隔符读取 git log (最安全可靠的方式) + # ============================================ + 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 - TOTAL_COMMITS=$(wc -l < /tmp/commits.txt) - echo "✓ 找到 $TOTAL_COMMITS 个提交" + # ============================================ + # 构建 CHANGELOG 条目 + # ============================================ + echo "📄 构建 CHANGELOG 条目..." - # Python 脚本生成 CHANGELOG - python3 << 'PYSCRIPT' - import re - import json - import sys - from datetime import datetime + REPO_URL="${{ github.server_url }}/${{ github.repository }}" - # 读取配置 - changelog_version = "${{ env.CHANGELOG_VERSION }}" - gitea_server = "${{ env.GITEA_SERVER }}" - repo = "${{ github.repository }}" - section_title = "${{ env.CHANGELOG_SECTION_TITLE }}" - contributors_title = "${{ env.CHANGELOG_CONTRIBUTORS_TITLE }}" + # 初始化条目内容 + NEW_ENTRY="## :bookmark: ${CHANGELOG_VERSION}\n\n" + NEW_ENTRY="${NEW_ENTRY}${CHANGELOG_SECTION_TITLE}\n\n" - # 读取忽略模式 - 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() + # 处理每个提交 + for ((i=0; i<$VALID_COUNT; i++)); do + hash="${COMMITS_HASH[$i]}" + author="${COMMITS_AUTHOR[$i]}" + message="${COMMITS_MESSAGE[$i]}" + body="${COMMITS_BODY[$i]}" - # 读取提交 - with open('/tmp/commits.txt', 'r', encoding='utf-8') as f: - lines = f.readlines() + echo " [$((i+1))/$VALID_COUNT] ${hash:0:7}: ${message:0:60}..." - commits = [] - skipped_commits = [] - for line in lines: - line = line.strip() - if not line or '|' not in line: - continue + # 构建提交链接 + short_hash="${hash:0:7}" + COMMIT_LINK="([${short_hash}](${REPO_URL}/commit/${hash}))" - commit_hash, message = line.split('|', 1) + # 处理 body (如果存在) + if [ -n "$body" ]; then + # 移除首尾空行,保留中间的空行 + cleaned_body=$(echo "$body" | sed -e :a -e '/^\s*$/d;') - # 检查是否应该忽略 - should_ignore = False - matched_pattern = None - for pattern in ignore_patterns: - if re.search(pattern, message): - should_ignore = True - matched_pattern = pattern - break + if [ -n "$cleaned_body" ]; then + # 有 body: 主题换行后接 body,然后是链接和作者 + # Markdown 需要两个空格来实现换行 + NEW_ENTRY="${NEW_ENTRY}- ${message} \n" - if should_ignore: - skipped_commits.append((message, matched_pattern)) - else: - commits.append({'hash': commit_hash, 'message': message}) + # 将 body 按行添加,每行缩进两个空格 + while IFS= read -r line; do + NEW_ENTRY="${NEW_ENTRY} ${line} \n" + done <<< "$cleaned_body" - # 输出统计 - print(f"📊 Commit Statistics:") - print(f" Total: {len(lines)}") - print(f" Valid: {len(commits)}") - print(f" Skipped: {len(skipped_commits)}") - print() + # 在最后一行添加链接和作者 + NEW_ENTRY="${NEW_ENTRY} ${COMMIT_LINK}" + if [ -n "$author" ]; then + NEW_ENTRY="${NEW_ENTRY} by @${author}" + fi + 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: - print("⏭️ Skipped Commits:") - for msg, pattern in skipped_commits: - print(f" ⏭️ Skipping: {msg[:80]}... (matched: {pattern})") - print() + echo "" + echo "👥 收集贡献者..." - if not commits: - print("⚠️ No valid commits found") - with open('/tmp/changelog_updated.txt', 'w') as f: - f.write('false') - sys.exit(0) + # 收集贡献者 (去除 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 - # 收集贡献者 - contributors = set() - for commit in commits: - # 从提交消息中提取可能的用户名(如果使用了 @username 格式) - # 这里简化处理,实际可以通过 git log --format='%an|%ae' 获取 - pass + if [ -n "$CONTRIBUTORS" ]; then + CONTRIBUTOR_COUNT=$(echo "$CONTRIBUTORS" | wc -l) + echo "✓ 找到 ${CONTRIBUTOR_COUNT} 个贡献者" - # 生成 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" + NEW_ENTRY="${NEW_ENTRY}\n${{ env.CHANGELOG_CONTRIBUTORS_TITLE }}\n\n" - 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" + while IFS= read -r name; do + [ -z "$name" ] && continue + NEW_ENTRY="${NEW_ENTRY}\n" + NEW_ENTRY="${NEW_ENTRY} \"${name}\"\n" + NEW_ENTRY="${NEW_ENTRY}\n" + done <<< "$CONTRIBUTORS" + fi - # 如果有贡献者信息,可以在这里添加 - # changelog_content += f"\n{contributors_title}\n\n" + NEW_ENTRY="${NEW_ENTRY}\n---\n" - changelog_content += "\n---\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}" + if [ -n "$body" ]; then + cleaned_body=$(echo "$body" | sed -e :a -e '/^\s*$/d;') + if [ -n "$cleaned_body" ]; then + NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES}- ${message} \n" + while IFS= read -r line; do + NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES} ${line} \n" + done <<< "$cleaned_body" + NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES} ${COMMIT_LINK}" + else + NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES}- ${message} ${COMMIT_LINK}" + fi + else + NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES}- ${message} ${COMMIT_LINK}" + fi - # 检查内容是否真的改变 - content_changed = (new_content != existing_content) - version_exists = f"## :bookmark: {changelog_version}" in existing_content + if [ -n "$author" ]; then + NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES} by @${author}" + fi - if content_changed: - with open(changelog_file, 'w', encoding='utf-8') as f: - f.write(new_content) + NEW_COMMIT_ENTRIES="${NEW_COMMIT_ENTRIES}\n" + 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" - if [ "$UPDATED" = "true" ]; then - echo "" - echo "✅ CHANGELOG 生成完成" + 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 - echo "" - echo "ℹ️ 没有有效的提交,跳过 CHANGELOG 更新" + # 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 + echo "" + echo "✅ CHANGELOG 生成完成" echo "======================================" echo "" @@ -828,10 +936,6 @@ jobs: id: upload_assets if: steps.changelog.outputs.changelog_updated == 'true' run: | - echo "======================================" - echo "📎 上传 Release 附件" - echo "======================================" - cd ${{ env.REPO_DIR }} echo "🔍 提取 Release ID..."