diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..4ef8773 --- /dev/null +++ b/release.sh @@ -0,0 +1,221 @@ +#!/bin/bash +set -e + +# Git 自动发布脚本 +# 功能:检查分支、工作区,生成 tag,并在 Gitea 上创建 Release + +# 1. 检查分支 +branch=$(git rev-parse --abbrev-ref HEAD) +if [ "$branch" != "main" ]; then + echo "❌ 错误:请在 main 分支运行,当前是 $branch" + exit 1 +fi +echo "✅ 分支: $branch" + +# 可选:检查本地 Gitea 配置中的默认分支设置(如果仓库里包含 Gitea 配置) +# 读取 custom/conf/app.ini 中的 DEFAULT_BRANCH 值并给出警告(不强制退出) +if [ -f custom/conf/app.ini ]; then + DEFAULT_BRANCH=$(grep -i '^DEFAULT_BRANCH' custom/conf/app.ini | head -n1 | cut -d'=' -f2 | tr -d ' ') + if [ -n "$DEFAULT_BRANCH" ]; then + if [ "$DEFAULT_BRANCH" = "main" ]; then + echo "⚠️ 警告: Gitea 配置 custom/conf/app.ini 中 DEFAULT_BRANCH 设置为 'main',这可能存在风险" + echo " 如果希望强制中止发布,请在脚本中启用退出逻辑。" + else + echo "ℹ️ Gitea 配置 DEFAULT_BRANCH=$DEFAULT_BRANCH" + fi + fi +fi + +# 2. 检查工作区是否干净 +if [ -n "$(git status --porcelain)" ]; then + echo "❌ 错误:工作区有未提交的更改,请先提交或 stash" + git status + exit 1 +fi +echo "✅ 工作区干净" + +# 3. 更新代码 +echo "⬇️ 拉取远程代码..." +git fetch origin +git pull origin main +echo "✅ 已同步最新代码" + +# 4. 从 release.md 提取版本和说明 +if [ ! -f release.md ]; then + echo "❌ 未找到 release.md" + exit 1 +fi + +VERSION=$(grep "^## v" release.md | tail -n 1 | sed 's/^## //') +TAG_MESSAGE=$(awk "/^## $VERSION\$/{flag=1;next}/^## v/{if(flag) flag=0}flag" release.md) + +# 过滤掉 emoji(4 字节 unicode,范围 U+10000 - U+10FFFF),避免 utf8mb4 字符导致服务器侧 collation 问题 +# 仅在发送到 Gitea 时使用过滤后的内容,保留原始 TAG_MESSAGE 用于本地 tag 注释 +RELEASE_BODY=$(printf '%s' "$TAG_MESSAGE" | perl -CSD -0777 -pe 's/[\x{10000}-\x{10FFFF}]//g') + +if [ -z "$VERSION" ]; then + echo "❌ release.md 中未找到版本号" + exit 1 +fi + +echo "📝 版本号: $VERSION" +echo "说明:" +echo "$TAG_MESSAGE" + +# 5. 创建 tag(如已存在则删除后重建) +if git rev-parse "$VERSION" >/dev/null 2>&1; then + echo "⚠️ 标签 $VERSION 已存在,删除旧标签..." + git tag -d "$VERSION" + git push origin ":refs/tags/$VERSION" +fi + +git tag -a "$VERSION" -m "$TAG_MESSAGE" +echo "✅ 已创建 tag $VERSION" + +# 6. 推送代码和 tag +echo "🚀 推送到远程..." +git push origin main +git push origin "$VERSION" + +# 7. 创建 Gitea Release +echo "" +echo "🌐 创建 Gitea Release..." + +# 自动解析 GITEA_URL 和 REPO +remote_url=$(git config --get remote.origin.url) +if [[ "$remote_url" == ssh://git@biboer.cn:21174/* ]]; then + # 特殊处理 biboer.cn:21174 的情况,Web 界面在 /gitea 路径下 + GITEA_URL="https://biboer.cn/gitea" + GITEA_REPO=$(echo "$remote_url" | sed 's|ssh://git@biboer\.cn:21174/||' | sed 's|\.git$||') +elif [[ "$remote_url" =~ ^ssh://([^/]+)/([^/]+)/(.+)\.git$ ]]; then + GITEA_URL="https://${BASH_REMATCH[1]}" + GITEA_REPO="${BASH_REMATCH[2]}/${BASH_REMATCH[3]}" +elif [[ "$remote_url" =~ ^([^@]+@[^:]+):([^/]+)/(.+)\.git$ ]]; then + # git@biboer.cn:21174/gavin/note-to-mp.git + hostport=$(echo "${BASH_REMATCH[1]}" | cut -d@ -f2) + GITEA_URL="https://${hostport}" + GITEA_REPO="${BASH_REMATCH[2]}/${BASH_REMATCH[3]}" +else + echo "❌ 无法解析远程地址: $remote_url" + exit 1 +fi + + +if [ -z "$GITEA_TOKEN" ]; then + echo "⚠️ 未设置 GITEA_TOKEN,只推送了 tag,没有创建 Release" + exit 0 +fi + +# 如果远程已经存在该 Release(通过 tag 查询),先删除它(避免 409 Conflict) +echo "🔍 检查远程 Release 是否已存在..." +check_response=$(curl -s -w "\n%{http_code}" \ + -X GET "$GITEA_URL/api/v1/repos/$GITEA_REPO/releases/tags/$VERSION" \ + -H "Authorization: token $GITEA_TOKEN") + +check_http_code=$(echo "$check_response" | tail -n 1) +check_body=$(echo "$check_response" | sed '$d') + +if [ "$check_http_code" -eq 200 ]; then + release_id=$(echo "$check_body" | jq -r '.id') + echo "⚠️ 远程已存在 Release $VERSION (ID: $release_id),正在删除..." + delete_response=$(curl -s -w "\n%{http_code}" \ + -X DELETE "$GITEA_URL/api/v1/repos/$GITEA_REPO/releases/$release_id" \ + -H "Authorization: token $GITEA_TOKEN") + delete_http_code=$(echo "$delete_response" | tail -n 1) + if [ "$delete_http_code" -eq 204 ] || [ "$delete_http_code" -eq 200 ]; then + echo "✅ 已删除旧的 Release" + else + echo "⚠️ 删除旧 Release 失败 (HTTP $delete_http_code),继续尝试创建新的 Release" + fi +fi + +# 使用 jq 生成正确的 JSON +JSON_PAYLOAD=$(echo "$RELEASE_BODY" | jq -R -s -c --arg version "$VERSION" '{ + tag_name: $version, + name: $version, + body: ., + draft: false, + prerelease: false +}') + +# 尝试创建 Release,若返回 409(冲突),再查询并删除后重试一次 +echo "🔄 正在创建 Release(首次尝试)..." +response_full=$(curl -s -w "\n%{http_code}" \ + -X POST "$GITEA_URL/api/v1/repos/$GITEA_REPO/releases" \ + -H "Content-Type: application/json" \ + -H "Authorization: token $GITEA_TOKEN" \ + -d "$JSON_PAYLOAD") + +http_code=$(echo "$response_full" | tail -n 1) +resp_body=$(echo "$response_full" | sed '$d') + +if [ "$http_code" -eq 201 ]; then + echo "✅ Release 创建成功: $VERSION" + else + if [ "$http_code" -eq 409 ]; then + echo "⚠️ 创建 Release 返回 409 Conflict,尝试删除远程冲突的 Release 并重试..." + # 再次查询 Release id + check_response=$(curl -s -w "\n%{http_code}" \ + -X GET "$GITEA_URL/api/v1/repos/$GITEA_REPO/releases/tags/$VERSION" \ + -H "Authorization: token $GITEA_TOKEN") + check_http_code=$(echo "$check_response" | tail -n 1) + check_body=$(echo "$check_response" | sed '$d') + if [ "$check_http_code" -eq 200 ]; then + release_id=$(echo "$check_body" | jq -r '.id') + delete_response=$(curl -s -w "\n%{http_code}" \ + -X DELETE "$GITEA_URL/api/v1/repos/$GITEA_REPO/releases/$release_id" \ + -H "Authorization: token $GITEA_TOKEN") + delete_http_code=$(echo "$delete_response" | tail -n 1) + if [ "$delete_http_code" -eq 204 ] || [ "$delete_http_code" -eq 200 ]; then + echo "✅ 已删除冲突的 Release,准备重试创建..." + # 重试创建一次 + retry_response=$(curl -s -w "\n%{http_code}" \ + -X POST "$GITEA_URL/api/v1/repos/$GITEA_REPO/releases" \ + -H "Content-Type: application/json" \ + -H "Authorization: token $GITEA_TOKEN" \ + -d "$JSON_PAYLOAD") + retry_code=$(echo "$retry_response" | tail -n 1) + retry_body=$(echo "$retry_response" | sed '$d') + if [ "$retry_code" -eq 201 ]; then + echo "✅ Release 创建成功 (重试): $VERSION" + else + echo "❌ 重试创建 Release 仍然失败 (HTTP $retry_code)" + echo "响应: $retry_body" + fi + else + echo "❌ 删除冲突 Release 失败 (HTTP $delete_http_code),无法重试" + echo "响应: $delete_response" + fi + else + echo "❌ 查询冲突 Release 失败 (HTTP $check_http_code),响应: $check_body" + fi + else + # 如果是 500 并且包含 collation/utf8 相关错误,回退到英文 body 重试 + if [ "$http_code" -eq 500 ] || echo "$resp_body" | grep -qi "collation\|utf8\|Conversion from collation"; then + echo "⚠️ 检测到字符集/编码错误 (HTTP $http_code),尝试回退到英文说明并重试..." + ENGLISH_BODY="## Release Notes\n\nThis is release $VERSION: $RELEASE_TITLE\n\nFor detailed Chinese release notes, please see release.md in the repository.\n\nQuick start:\n\n\`\`\`bash\ngit pull origin main\ncd web && npm install\nnpm run dev\n\`\`\`" + JSON_PAYLOAD_EN=$(jq -n -c --arg version "$VERSION" --arg name "$VERSION" --arg body "$ENGLISH_BODY" '{tag_name: $version, name: $name, body: $body, draft: false, prerelease: false}') + echo "🔄 正在创建 Release(英文回退,重试)..." + en_resp=$(curl -s -w "\n%{http_code}" \ + -X POST "$GITEA_URL/api/v1/repos/$GITEA_REPO/releases" \ + -H "Content-Type: application/json" \ + -H "Authorization: token $GITEA_TOKEN" \ + -d "$JSON_PAYLOAD_EN") + en_code=$(echo "$en_resp" | tail -n 1) + en_body=$(echo "$en_resp" | sed '$d') + if [ "$en_code" -eq 201 ]; then + echo "✅ Release 创建成功 (英文回退): $VERSION" + else + echo "❌ 英文回退重试失败 (HTTP $en_code)" + echo "响应: $en_body" + fi + else + echo "❌ Release 创建失败,HTTP $http_code" + echo "响应: $resp_body" + fi + fi +fi + +echo "" +echo "🎉 发布完成!" +echo "📦 版本:$VERSION"