diff --git a/.github/workflows/sync-main-to-dev.yml b/.github/workflows/sync-main-to-dev.yml new file mode 100644 index 0000000..c46cbcf --- /dev/null +++ b/.github/workflows/sync-main-to-dev.yml @@ -0,0 +1,159 @@ +name: main 回灌 dev + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +concurrency: + group: sync-main-to-dev + cancel-in-progress: true + +jobs: + sync-main-to-dev: + name: 执行回灌同步 + runs-on: ubuntu-latest + steps: + - name: 检出代码 + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: 检查是否需要同步 + id: diff_check + shell: bash + run: | + set -euo pipefail + echo "开始检查 main 与 dev 的分支差异..." + git fetch origin main dev + ahead_count="$(git rev-list --count origin/dev..origin/main)" + echo "ahead_count=${ahead_count}" >> "$GITHUB_OUTPUT" + if [ "${ahead_count}" -eq 0 ]; then + echo "无需同步,dev 已包含 main 的最新提交。" + echo "has_changes=false" >> "$GITHUB_OUTPUT" + else + echo "检测到 ${ahead_count} 个待同步提交,准备创建或复用同步 PR。" + echo "has_changes=true" >> "$GITHUB_OUTPUT" + fi + + - name: 创建或复用同步 PR + id: sync_pr + if: steps.diff_check.outputs.has_changes == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: | + set -euo pipefail + echo "permission_blocked=false" >> "$GITHUB_OUTPUT" + existing_number="$(gh pr list --base dev --head main --state open --json number --jq '.[0].number // empty')" + + if [ -n "${existing_number}" ]; then + pr_number="${existing_number}" + pr_url="$(gh pr view "${pr_number}" --json url --jq '.url')" + echo "复用已有同步 PR:#${pr_number}" + echo "created=false" >> "$GITHUB_OUTPUT" + else + body_file="$(mktemp)" + error_file="$(mktemp)" + { + echo "## 自动回灌:\`main -> dev\`" + echo + echo "- 触发条件:\`main\` 分支出现新提交(含贡献者直接合并到 \`main\` 的 PR)" + echo "- 目标:让 \`dev\` 持续吸收 \`main\` 的更新,避免发布前集中冲突" + echo + echo "### 合并建议" + echo "- 无冲突:直接合并该 PR(建议 \`Merge commit\`)" + echo "- 有冲突:在该 PR 内解决冲突后再合并" + } > "${body_file}" + + if pr_url="$(gh pr create \ + --base dev \ + --head main \ + --title "🔁 chore(sync): 回灌 main 到 dev" \ + --body-file "${body_file}" 2>"${error_file}")"; then + pr_number="${pr_url##*/}" + echo "已创建同步 PR:#${pr_number}" + echo "created=true" >> "$GITHUB_OUTPUT" + else + error_message="$(tr '\n' ' ' < "${error_file}")" + if printf '%s' "${error_message}" | grep -Fq "GitHub Actions is not permitted to create or approve pull requests"; then + echo "::warning::仓库未开启“Allow GitHub Actions to create and approve pull requests”,已跳过自动创建同步 PR。" + echo "permission_blocked=true" >> "$GITHUB_OUTPUT" + echo "created=false" >> "$GITHUB_OUTPUT" + echo "pr_number=" >> "$GITHUB_OUTPUT" + echo "pr_url=" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "::error::创建同步 PR 失败:${error_message}" + exit 1 + fi + fi + + echo "pr_number=${pr_number}" >> "$GITHUB_OUTPUT" + echo "pr_url=${pr_url}" >> "$GITHUB_OUTPUT" + + - name: 检查合并状态 + id: merge_state + if: steps.diff_check.outputs.has_changes == 'true' && steps.sync_pr.outputs.permission_blocked != 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: | + set -euo pipefail + pr_number="${{ steps.sync_pr.outputs.pr_number }}" + mergeable="$(gh pr view "${pr_number}" --json mergeable --jq '.mergeable')" + merge_state_status="$(gh pr view "${pr_number}" --json mergeStateStatus --jq '.mergeStateStatus')" + echo "PR #${pr_number} 合并状态:mergeable=${mergeable}, mergeStateStatus=${merge_state_status}" + echo "mergeable=${mergeable}" >> "$GITHUB_OUTPUT" + echo "merge_state_status=${merge_state_status}" >> "$GITHUB_OUTPUT" + + - name: 可合并时开启自动合并 + id: auto_merge + if: steps.diff_check.outputs.has_changes == 'true' && steps.sync_pr.outputs.permission_blocked != 'true' && steps.merge_state.outputs.mergeable == 'MERGEABLE' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: | + set -euo pipefail + pr_number="${{ steps.sync_pr.outputs.pr_number }}" + if gh pr merge "${pr_number}" --merge --auto; then + echo "已为 PR #${pr_number} 开启自动合并。" + echo "result=enabled" >> "$GITHUB_OUTPUT" + else + echo "::warning::自动合并开启失败,请手动处理并合并该 PR。" + echo "result=failed" >> "$GITHUB_OUTPUT" + fi + + - name: 写入执行摘要 + if: always() + shell: bash + run: | + { + echo "## main 回灌 dev 执行结果" + if [ "${{ steps.diff_check.outputs.has_changes }}" != "true" ]; then + echo "- 状态:无需同步(dev 已包含 main 最新提交)" + exit 0 + fi + if [ "${{ steps.sync_pr.outputs.permission_blocked }}" = "true" ]; then + echo "- 状态:已跳过自动创建同步 PR" + echo "- 原因:仓库未开启 GitHub Actions 创建与审批 Pull Request 权限" + echo "- 处理:前往 Settings -> Actions -> General -> Workflow permissions,开启 Allow GitHub Actions to create and approve pull requests" + echo "- 兜底:由维护者手动执行 main 到 dev 合并,或开启该设置后重新运行 workflow" + exit 0 + fi + echo "- PR:${{ steps.sync_pr.outputs.pr_url }}" + echo "- 可合并状态:${{ steps.merge_state.outputs.mergeable }}" + echo "- 合并状态详情:${{ steps.merge_state.outputs.merge_state_status }}" + if [ "${{ steps.merge_state.outputs.mergeable }}" = "CONFLICTING" ]; then + echo "- 结论:检测到冲突,需要手动处理后合并" + elif [ "${{ steps.auto_merge.outputs.result }}" = "enabled" ]; then + echo "- 结论:已启用自动合并(满足保护规则后将自动入 dev)" + else + echo "- 结论:PR 已创建/复用,请按分支策略人工合并" + fi + } >> "$GITHUB_STEP_SUMMARY" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 561dd32..b89e554 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,7 +79,14 @@ Because external pull requests are merged directly into `main`, maintainers must ### 1. Sync `main` -> `dev` (required) -Every change merged into `main` must be synced into `dev`: +This repository provides automatic sync via GitHub Actions workflow: + +- `.github/workflows/sync-main-to-dev.yml` +- Trigger: every push to `main` +- Behavior: create/reuse a PR from `main` to `dev`; if mergeable, it tries to enable auto-merge +- Prerequisite: in `Settings -> Actions -> General -> Workflow permissions`, enable `Allow GitHub Actions to create and approve pull requests`; otherwise the workflow will skip PR creation and only emit a warning summary + +Manual fallback (when conflicts or automation is unavailable): ```bash git checkout dev @@ -114,7 +121,7 @@ git push origin v0.6.0 ### 4. Sync `main` back to `dev` after release -After the release, sync `main` back into `dev` again: +After the release, the same automation still applies. If needed, you can run the workflow manually (`workflow_dispatch`) or execute the fallback commands: ```bash git checkout dev diff --git a/CONTRIBUTING.zh-CN.md b/CONTRIBUTING.zh-CN.md index 20cc60e..3e79997 100644 --- a/CONTRIBUTING.zh-CN.md +++ b/CONTRIBUTING.zh-CN.md @@ -79,7 +79,14 @@ feature/* / fix/* -> dev -> release/* -> main -> tag(vX.Y.Z) ### 1. main → dev 同步(必做) -任何合入 `main` 的变更,都必须同步到 `dev`: +仓库已提供 GitHub Actions 自动同步机制: + +- `.github/workflows/sync-main-to-dev.yml` +- 触发时机:每次 `main` 分支有新的 push +- 行为:自动创建或复用 `main` 到 `dev` 的同步 PR;若可合并,则尝试开启自动合并 +- 前置条件:需在 `Settings -> Actions -> General -> Workflow permissions` 中开启 `Allow GitHub Actions to create and approve pull requests`,否则 workflow 只会输出告警摘要并跳过建 PR + +当出现冲突,或自动化暂不可用时,使用以下手动兜底方式: ```bash git checkout dev @@ -114,7 +121,7 @@ git push origin v0.6.0 ### 4. main 回流到 dev(发版后必做) -发布完成后,再次将 `main` 回流到 `dev`,确保开发线与发布线一致: +发布完成后,仍沿用同一套自动化流程;如有需要,也可以手动触发 `workflow_dispatch`,或执行以下兜底命令,确保开发线与发布线一致: ```bash git checkout dev