From 3054338cf75796d3c77d90c26a41641150154fde Mon Sep 17 00:00:00 2001 From: csh Date: Mon, 20 Jul 2026 18:13:58 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20feat(ci):=20Release=20Notes=20?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E5=A2=9E=E9=87=8F=E5=B1=95=E7=A4=BA=EF=BC=8C?= =?UTF-8?q?=E6=AD=A3=E5=BC=8F=E7=89=88=E8=A6=86=E7=9B=96=E6=95=B4=E4=B8=AA?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=91=A8=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CHANGELOG.md 仍保持整个版本累积(strip 模式),但 Release Notes 改为: - 预发布(rc):只展示相对紧邻上一个 tag 的增量(如 rc1→rc2 新提交), 避免同版本各 rc 之间大量重复。 - 正式版:跳过同版本 rc,基准落到上一个不同版本 tag, 覆盖整个版本开发周期的全量提交。 基准 tag 按 RELEASE_IS_PRERELEASE 区分,Release Notes 独立用 git log 重新生成。 --- .gitea/workflows/changelog_and_release.yml | 179 ++++++++++++++++++--- 1 file changed, 155 insertions(+), 24 deletions(-) diff --git a/.gitea/workflows/changelog_and_release.yml b/.gitea/workflows/changelog_and_release.yml index 05c4c18..e7de56f 100644 --- a/.gitea/workflows/changelog_and_release.yml +++ b/.gitea/workflows/changelog_and_release.yml @@ -490,6 +490,38 @@ jobs: COMMIT_RANGE="${PREVIOUS_TAG}..${VERSION}" fi + # 计算 Release Notes 的增量基准 tag,规则按当前发布是否为预发布(rc)区分: + # - 预发布(rc):基准 = 紧邻的上一个 tag(不跳过同版本 rc) + # 例如 0.0.5-rc2 的基准是 0.0.5-rc1,Release Notes 只展示 rc1→rc2 的新提交, + # 避免同一版本各 rc 之间大量重复。 + # - 正式版:基准 = 上一个"不同 CHANGELOG 版本"的 tag(即上面的 PREVIOUS_TAG) + # 例如 0.0.5 正式版会跳过 0.0.5-rc1/rc2,基准落到 0.0.4-rc2, + # Release Notes 覆盖整个 0.0.5 开发周期的全量提交。 + # 注意:CHANGELOG.md 始终是整个版本的累积(strip 模式),与此处无关。 + if [[ "${RELEASE_IS_PRERELEASE}" == "true" ]]; then + NOTES_BASE_TAG="" + while IFS= read -r tag; do + [ -z "$tag" ] && continue + if [ "$tag" != "$VERSION" ]; then + NOTES_BASE_TAG="$tag" + break + fi + done <<< "$ALL_TAGS" + echo "🏷️ 预发布:Release Notes 基准 = 紧邻上一个 tag" + else + NOTES_BASE_TAG="$PREVIOUS_TAG" + echo "🏷️ 正式版:Release Notes 基准 = 上一个不同版本 tag(覆盖整个版本周期)" + fi + + if [ -z "$NOTES_BASE_TAG" ]; then + echo "ℹ️ 未找到基准 tag,Release Notes 将包含全部提交" + RELEASE_NOTES_RANGE="HEAD" + else + echo "📍 Release Notes 增量基准: $NOTES_BASE_TAG" + RELEASE_NOTES_RANGE="${NOTES_BASE_TAG}..${VERSION}" + fi + echo "RELEASE_NOTES_RANGE=$RELEASE_NOTES_RANGE" >> $GITHUB_ENV + echo "" echo "🔍 扫描提交记录..." @@ -918,40 +950,139 @@ jobs: if: steps.changelog.outputs.changelog_updated == 'true' run: | echo "======================================" - echo "📄 提取 Release Notes" + echo "📄 生成增量 Release Notes" echo "======================================" cd ${{ env.REPO_DIR }} - python3 << 'PYSCRIPT' - import re + # Release Notes 只展示本次 rc 相对上一个 tag 的增量提交 + # (CHANGELOG.md 仍是整个版本的累积,二者用途不同) + RELEASE_NOTES_RANGE="${{ env.RELEASE_NOTES_RANGE }}" + REPO_URL="${{ github.server_url }}/${{ github.repository }}" + echo "📊 增量范围: $RELEASE_NOTES_RANGE" - changelog_version = "${{ env.CHANGELOG_VERSION }}" + TEMP_COMMITS=$(mktemp) + git log $RELEASE_NOTES_RANGE --no-merges --reverse \ + --format="COMMIT_START%n%H%n%an%n%s%nBODY_START%n%b%nBODY_END" > "$TEMP_COMMITS" - with open('CHANGELOG.md', 'r', encoding='utf-8') as f: - content = f.read() + declare -a COMMITS_HASH + declare -a COMMITS_AUTHOR + declare -a COMMITS_MESSAGE + declare -a COMMITS_BODY - # 匹配版本区块 - pattern = rf'## :bookmark: {re.escape(changelog_version)}\s*\n(.*?)(?=\n---\n|\n## :bookmark: |\Z)' - match = re.search(pattern, content, re.DOTALL) + IFS='|' read -ra IGNORE_PATTERNS <<< "${{ env.IGNORE_PATTERNS_PROCESSED }}" - if match: - version_content = match.group(1).strip() - # 清理多余的空行 - version_content = re.sub(r'\n\s*\n\s*\n+', '\n\n', version_content).strip() - # 移除可能的分隔线 - version_content = re.sub(r'^-{3,}\s*\n', '', version_content) - print(f"✓ Found changelog for version {changelog_version}") - print(f"📊 Content length: {len(version_content)} characters") - else: - version_content = "⚠️ No changelog found for this version" - print(f"⚠️ WARNING: Version {changelog_version} not found in CHANGELOG") + COMMIT_INDEX=0 + CURRENT_HASH="" + CURRENT_AUTHOR="" + CURRENT_MESSAGE="" + CURRENT_BODY="" + IN_BODY=false - with open('/tmp/release-body.txt', 'w', encoding='utf-8') as f: - f.write(version_content) + flush_commit() { + [ -z "$CURRENT_HASH" ] && return + for pattern in "${IGNORE_PATTERNS[@]}"; do + [ -z "$pattern" ] && continue + if echo "$CURRENT_MESSAGE" | grep -qiE "$pattern"; then + return + fi + done + 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)) + } - print("✅ Release notes generated successfully") - PYSCRIPT + while IFS= read -r line; do + if [[ "$line" == "COMMIT_START" ]]; then + flush_commit + CURRENT_HASH="" + CURRENT_AUTHOR="" + CURRENT_MESSAGE="" + CURRENT_BODY="" + IN_BODY=false + elif [[ "$line" == "BODY_START" ]]; then + IN_BODY=true + CURRENT_BODY="" + elif [[ "$line" == "BODY_END" ]]; then + 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" + flush_commit + rm -f "$TEMP_COMMITS" + + VALID_COUNT=$COMMIT_INDEX + echo "✅ 增量有效提交: $VALID_COUNT" + + NOTES="${CHANGELOG_SECTION_TITLE}\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]}" + short_hash="${hash:0:7}" + COMMIT_LINK="([${short_hash}](${REPO_URL}/commit/${hash}))" + AUTHOR_INFO="" + [ -n "$author" ] && AUTHOR_INFO=" by @${author}" + + if [ -n "$body" ]; then + cleaned_body=$(echo "$body" | sed -e :a -e '/^\s*$/d;') + else + cleaned_body="" + fi + + if [ -n "$cleaned_body" ]; then + NOTES="${NOTES}- ${message} \n" + while IFS= read -r bline; do + [ -z "$bline" ] && continue + NOTES="${NOTES} ${bline} \n" + done <<< "$cleaned_body" + NOTES="${NOTES} ${COMMIT_LINK}${AUTHOR_INFO}\n" + else + NOTES="${NOTES}- ${message} ${COMMIT_LINK}${AUTHOR_INFO}\n" + fi + done + + # 增量贡献者(同样过滤 bot) + if [ "$RELEASE_NOTES_RANGE" = "HEAD" ]; then + CONTRIBUTORS=$(git log --pretty=format:"%an" --no-merges) + else + CONTRIBUTORS=$(git log $RELEASE_NOTES_RANGE --pretty=format:"%an" --no-merges) + fi + CONTRIBUTORS=$(echo "$CONTRIBUTORS" | \ + grep -v "github-actions\[bot\]" | \ + grep -v "dependabot\[bot\]" | \ + grep -v "renovate\[bot\]" | \ + grep -vxF "${{ env.GIT_USER_NAME }}" | \ + sort -u) + + if [ -n "$CONTRIBUTORS" ]; then + NOTES="${NOTES}\n${{ env.CHANGELOG_CONTRIBUTORS_TITLE }}\n\n" + while IFS= read -r name; do + [ -z "$name" ] && continue + NOTES="${NOTES}\n" + NOTES="${NOTES} \"${name}\"\n" + NOTES="${NOTES}\n" + done <<< "$CONTRIBUTORS" + fi + + echo -e "$NOTES" > /tmp/release-body.txt + echo "✅ Release notes 生成完成 ($(wc -c < /tmp/release-body.txt) 字节)" echo "======================================" echo ""