aodw-skill 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.aodw/01-core/ai-interaction-rules.md +218 -0
- package/.aodw/01-core/ai-knowledge-rules.md +302 -0
- package/.aodw/01-core/ai-project-overview-rules.md +284 -0
- package/.aodw/01-core/aodw-constitution-summary.md +20 -0
- package/.aodw/01-core/aodw-constitution.md +419 -0
- package/.aodw/01-core/csf-thinking-framework.md +373 -0
- package/.aodw/01-core/git-discipline.md +226 -0
- package/.aodw/01-core/module-doc-rules.md +90 -0
- package/.aodw/02-workflow/aodw-development-stages.md +235 -0
- package/.aodw/02-workflow/rt-id-generation-rules.md +267 -0
- package/.aodw/02-workflow/rt-manager-summary.md +15 -0
- package/.aodw/02-workflow/rt-manager.md +399 -0
- package/.aodw/02-workflow/spec-full-profile-summary.md +13 -0
- package/.aodw/02-workflow/spec-full-profile.md +391 -0
- package/.aodw/02-workflow/spec-lite-profile.md +313 -0
- package/.aodw/02-workflow/ui-workflow-rules.md +334 -0
- package/.aodw/03-standards/ai-coding-rules-common.md +89 -0
- package/.aodw/03-standards/ai-coding-rules.md +370 -0
- package/.aodw/03-standards/stacks/java-springboot/ai-coding-rules-backend.md +100 -0
- package/.aodw/03-standards/stacks/python-fastapi/ai-coding-rules-backend.md +612 -0
- package/.aodw/03-standards/stacks/react-typescript/ai-coding-rules-frontend.md +291 -0
- package/.aodw/03-standards/stacks/vue2/ai-coding-rules-frontend.md +97 -0
- package/.aodw/03-standards/ui-kit/ui-kit.md +163 -0
- package/.aodw/04-auditors/aodw-development-auditor-rules.md +470 -0
- package/.aodw/04-auditors/aodw-full-auditor-rules.md +365 -0
- package/.aodw/04-auditors/aodw-requirement-auditor-rules.md +408 -0
- package/.aodw/05-tooling/ai-tools-init-rules.md +465 -0
- package/.aodw/06-project/ai-overview.md +116 -0
- package/.aodw/06-project/modules-index.yaml +11 -0
- package/.aodw/07-optimization/token-usage-analysis.md +253 -0
- package/.aodw/README.md +26 -0
- package/.aodw/RELEASE-CHECKLIST.md +144 -0
- package/.aodw/config.yaml +2 -0
- package/.aodw/manifest.yaml +98 -0
- package/.aodw/templates/SOURCE-TO-DISTRIBUTION-GUIDE.md +276 -0
- package/.aodw/templates/TEMPLATE-APPLICATION-GUIDE.md +246 -0
- package/.aodw/templates/aodw-kernel-loader-template.md +70 -0
- package/.aodw/templates/audit-report-template.md +232 -0
- package/.aodw/templates/changelog-template.md +16 -0
- package/.aodw/templates/checklists/coding-standards-template.md +110 -0
- package/.aodw/templates/csf-review-template.md +201 -0
- package/.aodw/templates/impact-template.md +17 -0
- package/.aodw/templates/invariants-template.md +12 -0
- package/.aodw/templates/module-readme-template.md +39 -0
- package/.aodw/templates/plan-lite-template.md +11 -0
- package/.aodw/templates/rt-decision-template.md +13 -0
- package/.aodw/templates/rt-intake-template.md +33 -0
- package/.aodw/templates/rt-meta-template.yaml +43 -0
- package/.aodw/templates/spec-lite-template.md +17 -0
- package/.aodw/templates/tests-template.md +13 -0
- package/.aodw/templates/tools-config/README.md +80 -0
- package/.aodw/templates/tools-config/backend/black.config.template.toml +6 -0
- package/.aodw/templates/tools-config/backend/pre-commit.config.template.yaml +16 -0
- package/.aodw/templates/tools-config/backend/ruff.config.template.toml +23 -0
- package/.aodw/templates/tools-config/frontend/eslint.config.template.json +113 -0
- package/.aodw/templates/tools-config/frontend/prettier.config.template.json +10 -0
- package/.aodw/templates/tools-config/frontend/tsconfig.paths.template.json +11 -0
- package/.aodw/workflow-guide.md +51 -0
- package/AODW_Adapters/README.md +143 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-check.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-done.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-full.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-governance.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-impact.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-init.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-invariants.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-lite.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-module.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-new.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-open.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-pause.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-resume.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-tests.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-upgrade.md +7 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw.md +35 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-check.md +16 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-done.md +16 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-full.md +14 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-governance.md +13 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-impact.md +13 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-init.md +13 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-invariants.md +13 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-lite.md +14 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-module.md +13 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-new.md +30 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-open.md +10 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-pause.md +12 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-resume.md +12 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-tests.md +13 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-upgrade.md +12 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw.md +18 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/claude/CLAUDE.md +17 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-check.md +30 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-done.md +52 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-full.md +31 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-governance.md +34 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-impact.md +25 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-init.md +75 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-invariants.md +29 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-lite.md +23 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-module.md +24 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-new.md +70 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-open.md +19 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-pause.md +19 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-resume.md +20 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-tests.md +26 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-upgrade.md +27 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw.md +69 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/deploypromote.md +20 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/featuretotester.md +32 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/deploy/feature_to_master_push_test_local.sh +390 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/deploy/promote_only.sh +210 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/deploy/rollback_prod.sh +99 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/rules/aodw.mdc +26 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-check.md +29 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-done.md +52 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-full.md +30 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-governance.md +33 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-impact.md +24 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-init.md +75 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-invariants.md +28 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-lite.md +22 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-module.md +23 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-new.md +92 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-open.md +18 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-pause.md +18 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-resume.md +19 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-tests.md +25 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-upgrade.md +26 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw.md +68 -0
- package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/GEMINI.md +17 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-analyze.md +15 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-complete.md +15 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-control.md +14 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-decide.md +16 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-governance.md +7 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-implement.md +16 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-init.md +7 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-intake.md +15 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-open.md +7 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-simplified.md +107 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-verify.md +14 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-analyze.md +24 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-complete.md +23 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-control.md +21 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-decide.md +26 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-governance.md +13 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-implement.md +21 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-init.md +13 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-intake.md +28 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-open.md +10 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-verify.md +20 -0
- package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw.md +18 -0
- package/AODW_Adapters/antigravity/.agent/rules/aodw.md +74 -0
- package/AODW_Adapters/claude/CLAUDE.md +70 -0
- package/AODW_Adapters/cursor/.cursor/commands/README.md +37 -0
- package/AODW_Adapters/cursor/.cursor/rules/aodw.mdc +77 -0
- package/AODW_Adapters/gemini/.agent/rules/aodw-analyze.md +15 -0
- package/AODW_Adapters/gemini/.agent/rules/aodw-complete.md +15 -0
- package/AODW_Adapters/gemini/.agent/rules/aodw-control.md +14 -0
- package/AODW_Adapters/gemini/.agent/rules/aodw-decide.md +16 -0
- package/AODW_Adapters/gemini/.agent/rules/aodw-governance.md +33 -0
- package/AODW_Adapters/gemini/.agent/rules/aodw-implement.md +16 -0
- package/AODW_Adapters/gemini/.agent/rules/aodw-init.md +75 -0
- package/AODW_Adapters/gemini/.agent/rules/aodw-intake.md +15 -0
- package/AODW_Adapters/gemini/.agent/rules/aodw-open.md +18 -0
- package/AODW_Adapters/gemini/.agent/rules/aodw-verify.md +14 -0
- package/AODW_Adapters/gemini/.agent/rules/aodw.md +70 -0
- package/AODW_Adapters/gemini/GEMINI.md +17 -0
- package/AODW_Adapters/general/.github/copilot-instructions.md +34 -0
- package/AODW_Adapters/general/AGENTS.md +70 -0
- package/README.md +118 -0
- package/bin/aodw.js +627 -0
- package/bin/commands/init-overview.js +801 -0
- package/bin/commands/init-tools.js +811 -0
- package/bin/commands/new.js +235 -0
- package/bin/commands/serve.js +79 -0
- package/bin/processors/index.js +109 -0
- package/bin/update-adapters-from-template.js +89 -0
- package/bin/utils/config.js +56 -0
- package/docs/README.md +26 -0
- package/docs/adapter-evaluation.md +55 -0
- package/docs/backend-guidelines.md +335 -0
- package/docs/frontend-guidelines.md +266 -0
- package/docs/installation-variants.md +88 -0
- package/docs/migration-guide-0.2.0.md +250 -0
- package/docs/platform-matrix.md +83 -0
- package/package.json +40 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 只处理【本地存在、远端不存在】的当前功能分支:
|
|
3
|
+
# 1) 合并到本地主干(master/main,取决于配置)
|
|
4
|
+
# 2) 推送主干到测试环境(server/master)
|
|
5
|
+
# 3) 删除本地功能分支
|
|
6
|
+
# 不触碰任何其他本地功能分支;不尝试任何远端功能分支操作。
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
CONFIG_FILE="${CONFIG_FILE:-project-config.json}"
|
|
10
|
+
|
|
11
|
+
# 可选开关
|
|
12
|
+
MERGE_NO_FF=${MERGE_NO_FF:-true} # true: --no-ff 保留合并记录;false: --ff-only
|
|
13
|
+
DELETE_LOCAL_FEATURE=${DELETE_LOCAL_FEATURE:-true} # 是否删除本地功能分支
|
|
14
|
+
|
|
15
|
+
need() { command -v "$1" >/dev/null 2>&1 || { echo "❌ 缺少依赖:$1"; exit 1; }; }
|
|
16
|
+
need git
|
|
17
|
+
need jq
|
|
18
|
+
|
|
19
|
+
echo "🚀 本地功能分支 → master → 推送测试(只处理当前分支)"
|
|
20
|
+
|
|
21
|
+
# ========== 错误处理和回滚机制 ==========
|
|
22
|
+
# 保存关键状态
|
|
23
|
+
ORIGINAL_BRANCH=""
|
|
24
|
+
ORIGINAL_COMMIT=""
|
|
25
|
+
MERGE_COMMIT=""
|
|
26
|
+
STASHED=0
|
|
27
|
+
ROLLBACK_NEEDED=false
|
|
28
|
+
PUSHED_TO_REMOTE=false
|
|
29
|
+
ERROR_OCCURRED=false
|
|
30
|
+
|
|
31
|
+
# 错误处理函数
|
|
32
|
+
handle_error() {
|
|
33
|
+
# 防止错误处理函数本身出错导致无限循环
|
|
34
|
+
if [ "$ERROR_OCCURRED" = "true" ]; then
|
|
35
|
+
echo "❌ 错误处理过程中再次发生错误,强制退出"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
ERROR_OCCURRED=true
|
|
39
|
+
|
|
40
|
+
local exit_code=${1:-$?}
|
|
41
|
+
local line_number=${2:-$LINENO}
|
|
42
|
+
local error_message="${3:-未知错误}"
|
|
43
|
+
|
|
44
|
+
echo ""
|
|
45
|
+
echo "❌ ========================================"
|
|
46
|
+
echo "❌ 脚本执行失败!"
|
|
47
|
+
echo "❌ ========================================"
|
|
48
|
+
echo "❌ 错误位置:第 ${line_number} 行"
|
|
49
|
+
echo "❌ 错误信息:${error_message}"
|
|
50
|
+
echo "❌ 退出码:${exit_code}"
|
|
51
|
+
echo ""
|
|
52
|
+
|
|
53
|
+
# 执行回滚
|
|
54
|
+
if [ "$ROLLBACK_NEEDED" = "true" ]; then
|
|
55
|
+
echo "🔄 开始执行回滚..."
|
|
56
|
+
rollback_changes
|
|
57
|
+
else
|
|
58
|
+
echo "ℹ️ 无需回滚(未执行关键操作)"
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# 恢复工作状态
|
|
62
|
+
restore_work_state
|
|
63
|
+
|
|
64
|
+
echo ""
|
|
65
|
+
echo "❌ ========================================"
|
|
66
|
+
echo "❌ 脚本执行失败,请检查错误信息并手动处理"
|
|
67
|
+
echo "❌ ========================================"
|
|
68
|
+
echo ""
|
|
69
|
+
exit $exit_code
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# 回滚函数
|
|
73
|
+
rollback_changes() {
|
|
74
|
+
echo "🔄 执行回滚操作..."
|
|
75
|
+
|
|
76
|
+
# 如果已经合并,尝试撤销合并
|
|
77
|
+
if [ -n "$MERGE_COMMIT" ] && [ "$MERGE_COMMIT" != "" ] && [ -n "$ORIGINAL_COMMIT" ]; then
|
|
78
|
+
echo "🔄 撤销合并提交:$MERGE_COMMIT"
|
|
79
|
+
echo "🔄 回滚到提交:$ORIGINAL_COMMIT"
|
|
80
|
+
set +e
|
|
81
|
+
git reset --hard "$ORIGINAL_COMMIT" 2>/dev/null
|
|
82
|
+
RESET_RESULT=$?
|
|
83
|
+
set -e
|
|
84
|
+
|
|
85
|
+
if [ $RESET_RESULT -eq 0 ]; then
|
|
86
|
+
echo "✅ 已成功撤销合并"
|
|
87
|
+
else
|
|
88
|
+
echo "⚠️ 无法自动撤销合并,请手动执行:"
|
|
89
|
+
echo " git reset --hard $ORIGINAL_COMMIT"
|
|
90
|
+
fi
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# 如果已经推送到远程,提示用户
|
|
94
|
+
if [ "$PUSHED_TO_REMOTE" = "true" ]; then
|
|
95
|
+
echo ""
|
|
96
|
+
echo "⚠️ ========================================"
|
|
97
|
+
echo "⚠️ 重要提示:代码已推送到远程仓库"
|
|
98
|
+
echo "⚠️ ========================================"
|
|
99
|
+
echo "⚠️ 如果需要回滚远程分支,请手动执行:"
|
|
100
|
+
if [ -n "$ORIGINAL_COMMIT" ]; then
|
|
101
|
+
echo " git push $REMOTE_NAME $ORIGINAL_COMMIT:$REMOTE_MASTER --force"
|
|
102
|
+
else
|
|
103
|
+
echo " # 请先确定要回滚到的提交,然后执行强制推送"
|
|
104
|
+
fi
|
|
105
|
+
echo "⚠️ ========================================"
|
|
106
|
+
echo ""
|
|
107
|
+
fi
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# 恢复工作状态
|
|
111
|
+
restore_work_state() {
|
|
112
|
+
echo "🔙 恢复工作状态..."
|
|
113
|
+
|
|
114
|
+
# 切回原分支
|
|
115
|
+
if [ -n "$ORIGINAL_BRANCH" ] && [ "$ORIGINAL_BRANCH" != "" ]; then
|
|
116
|
+
echo "🔙 尝试切回原分支:$ORIGINAL_BRANCH"
|
|
117
|
+
set +e
|
|
118
|
+
git checkout "$ORIGINAL_BRANCH" >/dev/null 2>&1
|
|
119
|
+
CHECKOUT_RESULT=$?
|
|
120
|
+
set -e
|
|
121
|
+
|
|
122
|
+
if [ $CHECKOUT_RESULT -eq 0 ]; then
|
|
123
|
+
echo "✅ 已切回原分支:$ORIGINAL_BRANCH"
|
|
124
|
+
else
|
|
125
|
+
echo "⚠️ 无法切回原分支,当前分支:$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '未知')"
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# 恢复暂存的修改
|
|
130
|
+
if [ $STASHED -eq 1 ]; then
|
|
131
|
+
echo "📥 恢复暂存的修改..."
|
|
132
|
+
set +e
|
|
133
|
+
git stash pop >/dev/null 2>&1
|
|
134
|
+
STASH_RESULT=$?
|
|
135
|
+
set -e
|
|
136
|
+
|
|
137
|
+
if [ $STASH_RESULT -eq 0 ]; then
|
|
138
|
+
echo "✅ 已恢复暂存的修改"
|
|
139
|
+
else
|
|
140
|
+
echo "⚠️ 恢复暂存时发生冲突,请手动处理:"
|
|
141
|
+
echo " git stash list"
|
|
142
|
+
echo " git stash pop"
|
|
143
|
+
fi
|
|
144
|
+
fi
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# 注册错误处理(使用 EXIT 而不是 ERR,因为 ERR 在 set -e 时可能不会触发)
|
|
148
|
+
trap 'if [ $? -ne 0 ] && [ "$ERROR_OCCURRED" != "true" ]; then handle_error $? $LINENO "脚本执行过程中发生错误"; fi' EXIT
|
|
149
|
+
trap 'handle_error 130 $LINENO "脚本被中断(Ctrl+C)"' INT TERM
|
|
150
|
+
|
|
151
|
+
# ========== 首次初始化配置 ==========
|
|
152
|
+
if [ ! -f "$CONFIG_FILE" ]; then
|
|
153
|
+
echo "🧩 首次运行,初始化配置文件:$CONFIG_FILE"
|
|
154
|
+
read -r -p "本地主干分支名(默认 master): " project_branch
|
|
155
|
+
project_branch=${project_branch:-master}
|
|
156
|
+
|
|
157
|
+
read -r -p "远程名(默认 server): " remote_name
|
|
158
|
+
remote_name=${remote_name:-server}
|
|
159
|
+
|
|
160
|
+
read -r -p "服务器测试分支名(默认 master): " remote_master
|
|
161
|
+
remote_master=${remote_master:-master}
|
|
162
|
+
|
|
163
|
+
read -r -p "服务器生产分支名(默认 release): " remote_release
|
|
164
|
+
remote_release=${remote_release:-release}
|
|
165
|
+
|
|
166
|
+
# 与 promote_only.sh 保持同一套字段(方便后续发布)
|
|
167
|
+
read -r -p "是否使用 QA 标签发布?(y/N 默认否): " use_qa
|
|
168
|
+
use_qa_tag=false
|
|
169
|
+
if [[ "$use_qa" == "y" || "$use_qa" == "Y" ]]; then
|
|
170
|
+
use_qa_tag=true
|
|
171
|
+
fi
|
|
172
|
+
read -r -p "QA 标签前缀(默认 qa-ok/): " qa_tag_prefix
|
|
173
|
+
qa_tag_prefix=${qa_tag_prefix:-qa-ok/}
|
|
174
|
+
read -r -p "生产标签前缀(默认 prod-): " prod_tag_prefix
|
|
175
|
+
prod_tag_prefix=${prod_tag_prefix:-prod-}
|
|
176
|
+
|
|
177
|
+
cat > "$CONFIG_FILE" <<JSON
|
|
178
|
+
{
|
|
179
|
+
"project_branch": "$project_branch",
|
|
180
|
+
"remote_name": "$remote_name",
|
|
181
|
+
"remote_master": "$remote_master",
|
|
182
|
+
"remote_release": "$remote_release",
|
|
183
|
+
"use_qa_tag": $use_qa_tag,
|
|
184
|
+
"qa_tag_prefix": "$qa_tag_prefix",
|
|
185
|
+
"prod_tag_prefix": "$prod_tag_prefix"
|
|
186
|
+
}
|
|
187
|
+
JSON
|
|
188
|
+
echo "✅ 已创建配置文件:$CONFIG_FILE"
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
# ========== 读取配置 ==========
|
|
192
|
+
PROJECT_BRANCH=$(jq -r '.project_branch' "$CONFIG_FILE")
|
|
193
|
+
REMOTE_NAME=$(jq -r '.remote_name' "$CONFIG_FILE")
|
|
194
|
+
REMOTE_MASTER=$(jq -r '.remote_master' "$CONFIG_FILE")
|
|
195
|
+
|
|
196
|
+
# 验证配置是否读取成功
|
|
197
|
+
if [ -z "$PROJECT_BRANCH" ] || [ -z "$REMOTE_NAME" ] || [ -z "$REMOTE_MASTER" ]; then
|
|
198
|
+
handle_error 1 $LINENO "配置读取失败,请检查 $CONFIG_FILE。确保配置文件中包含 project_branch、remote_name 和 remote_master 字段"
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
# 验证配置值是否有效
|
|
202
|
+
if [ "$PROJECT_BRANCH" = "null" ] || [ "$REMOTE_NAME" = "null" ] || [ "$REMOTE_MASTER" = "null" ]; then
|
|
203
|
+
handle_error 1 $LINENO "配置值无效(null),请检查 $CONFIG_FILE"
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
echo "📦 配置:local=${PROJECT_BRANCH:-未定义} remote=${REMOTE_NAME:-未定义} test=${REMOTE_MASTER:-未定义}"
|
|
207
|
+
|
|
208
|
+
# ========== 确定"当前功能分支" ==========
|
|
209
|
+
CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)"
|
|
210
|
+
if [ "$CURRENT_BRANCH" = "HEAD" ]; then
|
|
211
|
+
handle_error 1 $LINENO "无法确定当前 Git 分支,请确保在 Git 仓库中运行此脚本"
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
FEATURE_BRANCH="$CURRENT_BRANCH"
|
|
215
|
+
ORIGINAL_BRANCH="$CURRENT_BRANCH"
|
|
216
|
+
|
|
217
|
+
# 保存当前提交(用于回滚)
|
|
218
|
+
ORIGINAL_COMMIT="$(git rev-parse HEAD 2>/dev/null)"
|
|
219
|
+
if [ -z "$ORIGINAL_COMMIT" ]; then
|
|
220
|
+
handle_error 1 $LINENO "无法获取当前提交哈希,请确保在有效的 Git 仓库中运行"
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
# 如果当前已经在主干分支,跳过合并,直接推送
|
|
224
|
+
SKIP_MERGE=false
|
|
225
|
+
if [ "$FEATURE_BRANCH" = "$PROJECT_BRANCH" ]; then
|
|
226
|
+
echo "ℹ️ 当前分支就是主干(${PROJECT_BRANCH:-未定义}),跳过合并步骤,直接推送"
|
|
227
|
+
SKIP_MERGE=true
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# ========== 保护现场(stash) ==========
|
|
231
|
+
if [ "$SKIP_MERGE" = "false" ]; then
|
|
232
|
+
if ! git diff --quiet || ! git diff --cached --quiet; then
|
|
233
|
+
echo "💾 暂存当前未提交修改..."
|
|
234
|
+
git add -A
|
|
235
|
+
if ! git stash push -m "auto-stash-before-feature-merge $(date +%F_%T)"; then
|
|
236
|
+
handle_error $? $LINENO "暂存未提交修改失败:git stash push"
|
|
237
|
+
fi
|
|
238
|
+
STASHED=1
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
# ========== 更新本地主干,减少合并冲突 ==========
|
|
242
|
+
echo "🔄 同步本地主干 ${PROJECT_BRANCH:-未定义} ..."
|
|
243
|
+
if ! git fetch "$REMOTE_NAME" --prune; then
|
|
244
|
+
handle_error $? $LINENO "获取远程仓库信息失败:git fetch ${REMOTE_NAME:-未定义} --prune。请检查网络连接和远程配置"
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
if ! git checkout "$PROJECT_BRANCH"; then
|
|
248
|
+
handle_error $? $LINENO "切换到主干分支失败:git checkout ${PROJECT_BRANCH:-未定义}"
|
|
249
|
+
fi
|
|
250
|
+
|
|
251
|
+
# 保存合并前的提交(用于回滚)
|
|
252
|
+
ORIGINAL_COMMIT="$(git rev-parse HEAD 2>/dev/null)"
|
|
253
|
+
if [ -z "$ORIGINAL_COMMIT" ]; then
|
|
254
|
+
handle_error 1 $LINENO "无法获取主干分支的提交哈希"
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
if ! git pull "$REMOTE_NAME" "$PROJECT_BRANCH"; then
|
|
258
|
+
handle_error $? $LINENO "拉取远程主干分支失败:git pull ${REMOTE_NAME:-未定义} ${PROJECT_BRANCH:-未定义}。可能存在冲突,请手动解决"
|
|
259
|
+
fi
|
|
260
|
+
|
|
261
|
+
# ========== 合并当前功能分支到主干 ==========
|
|
262
|
+
echo "🔁 合并 ${FEATURE_BRANCH:-未定义} → ${PROJECT_BRANCH:-未定义}"
|
|
263
|
+
set +e
|
|
264
|
+
if [ "$MERGE_NO_FF" = "true" ]; then
|
|
265
|
+
git merge --no-ff --no-edit "$FEATURE_BRANCH"
|
|
266
|
+
M=$?
|
|
267
|
+
else
|
|
268
|
+
git merge --ff-only "$FEATURE_BRANCH"
|
|
269
|
+
M=$?
|
|
270
|
+
fi
|
|
271
|
+
set -e
|
|
272
|
+
|
|
273
|
+
if [ $M -ne 0 ]; then
|
|
274
|
+
handle_error $M $LINENO "合并失败或冲突:git merge ${FEATURE_BRANCH:-未定义} → ${PROJECT_BRANCH:-未定义}。请解决冲突后重试"
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# 保存合并后的提交(用于回滚)
|
|
278
|
+
MERGE_COMMIT="$(git rev-parse HEAD 2>/dev/null)"
|
|
279
|
+
if [ -z "$MERGE_COMMIT" ]; then
|
|
280
|
+
handle_error 1 $LINENO "合并后无法获取提交哈希"
|
|
281
|
+
fi
|
|
282
|
+
ROLLBACK_NEEDED=true
|
|
283
|
+
|
|
284
|
+
echo "✅ 合并成功,合并提交:$MERGE_COMMIT"
|
|
285
|
+
else
|
|
286
|
+
# 已经在主干分支,只需要更新即可
|
|
287
|
+
echo "🔄 同步本地主干 ${PROJECT_BRANCH:-未定义} ..."
|
|
288
|
+
if ! git fetch "$REMOTE_NAME" --prune; then
|
|
289
|
+
handle_error $? $LINENO "获取远程仓库信息失败:git fetch ${REMOTE_NAME:-未定义} --prune。请检查网络连接和远程配置"
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
if ! git pull "$REMOTE_NAME" "$PROJECT_BRANCH"; then
|
|
293
|
+
handle_error $? $LINENO "拉取远程主干分支失败:git pull ${REMOTE_NAME:-未定义} ${PROJECT_BRANCH:-未定义}。可能存在冲突,请手动解决"
|
|
294
|
+
fi
|
|
295
|
+
|
|
296
|
+
# 保存当前提交(用于回滚)
|
|
297
|
+
ORIGINAL_COMMIT="$(git rev-parse HEAD 2>/dev/null)"
|
|
298
|
+
if [ -z "$ORIGINAL_COMMIT" ]; then
|
|
299
|
+
handle_error 1 $LINENO "无法获取当前提交哈希"
|
|
300
|
+
fi
|
|
301
|
+
fi
|
|
302
|
+
|
|
303
|
+
# ========== 推送主干到测试环境 ==========
|
|
304
|
+
# 确保变量已设置(防止在合并过程中丢失)
|
|
305
|
+
if [ -z "$REMOTE_MASTER" ] || [ "$REMOTE_MASTER" = "null" ]; then
|
|
306
|
+
REMOTE_MASTER=$(jq -r '.remote_master' "$CONFIG_FILE")
|
|
307
|
+
if [ -z "$REMOTE_MASTER" ] || [ "$REMOTE_MASTER" = "null" ]; then
|
|
308
|
+
handle_error 1 $LINENO "无法从配置文件读取 remote_master,请检查 $CONFIG_FILE"
|
|
309
|
+
fi
|
|
310
|
+
fi
|
|
311
|
+
if [ -z "$REMOTE_NAME" ] || [ "$REMOTE_NAME" = "null" ]; then
|
|
312
|
+
REMOTE_NAME=$(jq -r '.remote_name' "$CONFIG_FILE")
|
|
313
|
+
if [ -z "$REMOTE_NAME" ] || [ "$REMOTE_NAME" = "null" ]; then
|
|
314
|
+
handle_error 1 $LINENO "无法从配置文件读取 remote_name,请检查 $CONFIG_FILE"
|
|
315
|
+
fi
|
|
316
|
+
fi
|
|
317
|
+
|
|
318
|
+
# 验证远程仓库是否存在
|
|
319
|
+
if ! git remote get-url "$REMOTE_NAME" >/dev/null 2>&1; then
|
|
320
|
+
handle_error 1 $LINENO "远程仓库 '${REMOTE_NAME:-未定义}' 不存在,请检查 Git 远程配置:git remote -v"
|
|
321
|
+
fi
|
|
322
|
+
|
|
323
|
+
echo "⏫ 推送 ${PROJECT_BRANCH:-未定义} → ${REMOTE_NAME:-未定义}/${REMOTE_MASTER:-未定义}(测试环境)"
|
|
324
|
+
if ! git push "$REMOTE_NAME" "$PROJECT_BRANCH:$REMOTE_MASTER"; then
|
|
325
|
+
handle_error $? $LINENO "推送到远程失败:git push ${REMOTE_NAME:-未定义} ${PROJECT_BRANCH:-未定义}:${REMOTE_MASTER:-未定义}。请检查网络连接和权限"
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
# 推送成功后,标记需要回滚(如果后续步骤失败)
|
|
329
|
+
PUSHED_TO_REMOTE=true
|
|
330
|
+
ROLLBACK_NEEDED=true
|
|
331
|
+
|
|
332
|
+
# ========== 删除本地功能分支(仅本地!不动远端) ==========
|
|
333
|
+
if [ "$SKIP_MERGE" = "false" ] && [ "$DELETE_LOCAL_FEATURE" = "true" ]; then
|
|
334
|
+
echo "🧹 删除本地功能分支:$FEATURE_BRANCH"
|
|
335
|
+
# 先确保当前不在该分支
|
|
336
|
+
if ! git checkout "$PROJECT_BRANCH"; then
|
|
337
|
+
# 删除分支失败不是致命错误,但需要记录
|
|
338
|
+
echo "⚠️ 切换到主干分支失败(删除功能分支前),跳过删除操作"
|
|
339
|
+
else
|
|
340
|
+
set +e
|
|
341
|
+
git branch -d "$FEATURE_BRANCH" 2>/dev/null
|
|
342
|
+
DELETE_RESULT=$?
|
|
343
|
+
set -e
|
|
344
|
+
|
|
345
|
+
if [ $DELETE_RESULT -ne 0 ]; then
|
|
346
|
+
echo "⚠️ 无法安全删除(可能未完全合并),执行强制删除"
|
|
347
|
+
set +e
|
|
348
|
+
git branch -D "$FEATURE_BRANCH" 2>/dev/null
|
|
349
|
+
DELETE_RESULT=$?
|
|
350
|
+
set -e
|
|
351
|
+
if [ $DELETE_RESULT -eq 0 ]; then
|
|
352
|
+
echo "✅ 已强制删除功能分支:$FEATURE_BRANCH"
|
|
353
|
+
else
|
|
354
|
+
echo "⚠️ 删除功能分支失败,但这不是致命错误,请手动删除:git branch -D $FEATURE_BRANCH"
|
|
355
|
+
fi
|
|
356
|
+
else
|
|
357
|
+
echo "✅ 已删除功能分支:$FEATURE_BRANCH"
|
|
358
|
+
fi
|
|
359
|
+
fi
|
|
360
|
+
elif [ "$SKIP_MERGE" = "false" ]; then
|
|
361
|
+
echo "ℹ️ 按配置保留本地功能分支:$FEATURE_BRANCH"
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
# 所有操作成功,清除错误处理 trap(避免在正常退出时触发错误处理)
|
|
365
|
+
trap - EXIT INT TERM
|
|
366
|
+
|
|
367
|
+
# 清除回滚标记
|
|
368
|
+
ROLLBACK_NEEDED=false
|
|
369
|
+
PUSHED_TO_REMOTE=false
|
|
370
|
+
|
|
371
|
+
# 恢复工作状态(成功时)
|
|
372
|
+
if [ "$SKIP_MERGE" = "false" ] && [ -n "$ORIGINAL_BRANCH" ] && [ "$ORIGINAL_BRANCH" != "$PROJECT_BRANCH" ]; then
|
|
373
|
+
# 如果原分支还存在且不是主干,切回原分支
|
|
374
|
+
if git show-ref --verify --quiet "refs/heads/$ORIGINAL_BRANCH" 2>/dev/null; then
|
|
375
|
+
echo "🔙 切回原分支:$ORIGINAL_BRANCH"
|
|
376
|
+
git checkout "$ORIGINAL_BRANCH" >/dev/null 2>&1 || {
|
|
377
|
+
echo "⚠️ 无法切回原分支,但操作已成功完成"
|
|
378
|
+
}
|
|
379
|
+
fi
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
echo ""
|
|
383
|
+
echo "✅ ========================================"
|
|
384
|
+
if [ "$SKIP_MERGE" = "true" ]; then
|
|
385
|
+
echo "✅ 完成:直接推送 ${PROJECT_BRANCH:-未定义} → 测试环境"
|
|
386
|
+
else
|
|
387
|
+
echo "✅ 完成:${FEATURE_BRANCH:-未定义} → ${PROJECT_BRANCH:-未定义} → 推送测试"
|
|
388
|
+
fi
|
|
389
|
+
echo "✅ ========================================"
|
|
390
|
+
echo ""
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
CONFIG_FILE="${CONFIG_FILE:-project-config.json}"
|
|
5
|
+
|
|
6
|
+
# ===== 工具检查 =====
|
|
7
|
+
need() { command -v "$1" >/dev/null 2>&1 || { echo "❌ 缺少依赖:$1"; exit 1; }; }
|
|
8
|
+
need git
|
|
9
|
+
need jq
|
|
10
|
+
|
|
11
|
+
echo "🚀 Promote (测试 → 生产) 自动发布脚本启动..."
|
|
12
|
+
|
|
13
|
+
# ===== 首次运行:初始化配置 =====
|
|
14
|
+
if [ ! -f "$CONFIG_FILE" ]; then
|
|
15
|
+
echo "🧩 首次运行,开始初始化配置文件:$CONFIG_FILE"
|
|
16
|
+
read -r -p "本地主干分支名(默认 master): " project_branch
|
|
17
|
+
project_branch=${project_branch:-master}
|
|
18
|
+
|
|
19
|
+
# 默认远程名改为 server
|
|
20
|
+
read -r -p "远程名(默认 server): " remote_name
|
|
21
|
+
remote_name=${remote_name:-server}
|
|
22
|
+
|
|
23
|
+
read -r -p "服务器测试分支名(默认 master): " remote_master
|
|
24
|
+
remote_master=${remote_master:-master}
|
|
25
|
+
|
|
26
|
+
read -r -p "服务器生产分支名(默认 release): " remote_release
|
|
27
|
+
remote_release=${remote_release:-release}
|
|
28
|
+
|
|
29
|
+
read -r -p "是否使用 QA 标签发布?(y/N 默认否): " use_qa
|
|
30
|
+
use_qa_tag=false
|
|
31
|
+
if [[ "$use_qa" == "y" || "$use_qa" == "Y" ]]; then
|
|
32
|
+
use_qa_tag=true
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
read -r -p "QA 标签前缀(默认 qa-ok/): " qa_tag_prefix
|
|
36
|
+
qa_tag_prefix=${qa_tag_prefix:-qa-ok/}
|
|
37
|
+
|
|
38
|
+
read -r -p "生产标签前缀(默认 prod-): " prod_tag_prefix
|
|
39
|
+
prod_tag_prefix=${prod_tag_prefix:-prod-}
|
|
40
|
+
|
|
41
|
+
cat > "$CONFIG_FILE" <<JSON
|
|
42
|
+
{
|
|
43
|
+
"project_branch": "$project_branch",
|
|
44
|
+
"remote_name": "$remote_name",
|
|
45
|
+
"remote_master": "$remote_master",
|
|
46
|
+
"remote_release": "$remote_release",
|
|
47
|
+
"use_qa_tag": $use_qa_tag,
|
|
48
|
+
"qa_tag_prefix": "$qa_tag_prefix",
|
|
49
|
+
"prod_tag_prefix": "$prod_tag_prefix"
|
|
50
|
+
}
|
|
51
|
+
JSON
|
|
52
|
+
echo "✅ 已创建配置文件:$CONFIG_FILE"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# ===== 读取配置 =====
|
|
56
|
+
PROJECT_BRANCH=$(jq -r '.project_branch' "$CONFIG_FILE")
|
|
57
|
+
REMOTE_NAME=$(jq -r '.remote_name' "$CONFIG_FILE")
|
|
58
|
+
REMOTE_MASTER=$(jq -r '.remote_master' "$CONFIG_FILE")
|
|
59
|
+
REMOTE_RELEASE=$(jq -r '.remote_release' "$CONFIG_FILE")
|
|
60
|
+
USE_QA_TAG=$(jq -r '.use_qa_tag' "$CONFIG_FILE")
|
|
61
|
+
QA_TAG_PREFIX=$(jq -r '.qa_tag_prefix' "$CONFIG_FILE")
|
|
62
|
+
PROD_TAG_PREFIX=$(jq -r '.prod_tag_prefix' "$CONFIG_FILE")
|
|
63
|
+
|
|
64
|
+
echo "📦 使用配置: local=$PROJECT_BRANCH, remote=$REMOTE_NAME, test=$REMOTE_MASTER, prod=$REMOTE_RELEASE"
|
|
65
|
+
|
|
66
|
+
# ===== 保存当前开发状态 =====
|
|
67
|
+
CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD || echo HEAD)"
|
|
68
|
+
STASHED=0
|
|
69
|
+
if ! git diff --quiet || ! git diff --cached --quiet; then
|
|
70
|
+
echo "💾 暂存本地未提交变更..."
|
|
71
|
+
git add -A
|
|
72
|
+
git stash push -m "auto-stash-before-promote $(date +%F_%T)"
|
|
73
|
+
STASHED=1
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# ===== 临时分支名称 =====
|
|
77
|
+
TMP="__tmp_release_promote__"
|
|
78
|
+
|
|
79
|
+
# ===== 清理函数:确保临时分支和文件被清理 =====
|
|
80
|
+
cleanup() {
|
|
81
|
+
local exit_code=$?
|
|
82
|
+
echo ""
|
|
83
|
+
echo "🧹 清理临时资源..."
|
|
84
|
+
|
|
85
|
+
# 切换到非临时分支(避免删除当前所在分支)
|
|
86
|
+
if [ "$(git rev-parse --abbrev-ref HEAD)" = "$TMP" ]; then
|
|
87
|
+
git checkout "$PROJECT_BRANCH" >/dev/null 2>&1 || git checkout "$CURRENT_BRANCH" >/dev/null 2>&1 || true
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# 删除临时分支(如果存在)
|
|
91
|
+
if git show-ref --verify --quiet "refs/heads/$TMP"; then
|
|
92
|
+
echo "🗑️ 删除临时分支: $TMP"
|
|
93
|
+
git branch -D "$TMP" >/dev/null 2>&1 || true
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# 清理可能存在的其他临时分支(兼容旧版本)
|
|
97
|
+
for old_tmp in tmp_release_sync __tmp_release_promote__; do
|
|
98
|
+
if git show-ref --verify --quiet "refs/heads/$old_tmp"; then
|
|
99
|
+
echo "🗑️ 删除遗留临时分支: $old_tmp"
|
|
100
|
+
git branch -D "$old_tmp" >/dev/null 2>&1 || true
|
|
101
|
+
fi
|
|
102
|
+
done
|
|
103
|
+
|
|
104
|
+
# 删除临时变更日志文件
|
|
105
|
+
rm -f .promote_changelog_*.txt || true
|
|
106
|
+
|
|
107
|
+
# 恢复原工作分支和暂存
|
|
108
|
+
restore
|
|
109
|
+
|
|
110
|
+
if [ $exit_code -ne 0 ]; then
|
|
111
|
+
echo "⚠️ 脚本执行失败(退出码: $exit_code)"
|
|
112
|
+
fi
|
|
113
|
+
exit $exit_code
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
restore() {
|
|
117
|
+
echo "🔙 恢复原工作分支: $CURRENT_BRANCH"
|
|
118
|
+
git checkout "$CURRENT_BRANCH" >/dev/null 2>&1 || true
|
|
119
|
+
if [ $STASHED -eq 1 ]; then
|
|
120
|
+
echo "📥 恢复暂存变更..."
|
|
121
|
+
git stash pop || echo "⚠️ 恢复发生冲突,请手动处理。"
|
|
122
|
+
fi
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# 注册清理函数(在 EXIT、INT、TERM 信号时执行)
|
|
126
|
+
trap cleanup EXIT INT TERM
|
|
127
|
+
|
|
128
|
+
# ===== 脚本开始时清理可能存在的旧临时分支 =====
|
|
129
|
+
echo "🔍 检查并清理可能存在的旧临时分支..."
|
|
130
|
+
for old_tmp in tmp_release_sync __tmp_release_promote__; do
|
|
131
|
+
if git show-ref --verify --quiet "refs/heads/$old_tmp" 2>/dev/null; then
|
|
132
|
+
echo "🧹 发现遗留临时分支: $old_tmp,正在清理..."
|
|
133
|
+
if [ "$(git rev-parse --abbrev-ref HEAD)" = "$old_tmp" ]; then
|
|
134
|
+
git checkout "$PROJECT_BRANCH" >/dev/null 2>&1 || git checkout "$CURRENT_BRANCH" >/dev/null 2>&1 || true
|
|
135
|
+
fi
|
|
136
|
+
git branch -D "$old_tmp" >/dev/null 2>&1 || true
|
|
137
|
+
fi
|
|
138
|
+
done
|
|
139
|
+
|
|
140
|
+
# ===== 获取远端最新信息 =====
|
|
141
|
+
git fetch "$REMOTE_NAME" --prune
|
|
142
|
+
|
|
143
|
+
# ===== 确定测试基线(QA 标签或 HEAD) =====
|
|
144
|
+
if [ "$USE_QA_TAG" = "true" ]; then
|
|
145
|
+
TEST_BASE_COMMIT=$(git ls-remote --tags "$REMOTE_NAME" | awk -v pfx="refs/tags/$QA_TAG_PREFIX" '$2 ~ pfx {print $1 " " $2}' | sort -k2 | tail -n1 | awk '{print $1}')
|
|
146
|
+
[ -n "$TEST_BASE_COMMIT" ] || { echo "❌ 未找到任何 ${QA_TAG_PREFIX}* 标签"; exit 1; }
|
|
147
|
+
echo "✅ 使用 QA 标签作为基线: $TEST_BASE_COMMIT"
|
|
148
|
+
else
|
|
149
|
+
TEST_BASE_COMMIT=$(git ls-remote "$REMOTE_NAME" "$REMOTE_MASTER" | awk '{print $1}')
|
|
150
|
+
[ -n "$TEST_BASE_COMMIT" ] || { echo "❌ 获取 $REMOTE_NAME/$REMOTE_MASTER 失败"; exit 1; }
|
|
151
|
+
echo "✅ 使用 $REMOTE_NAME/$REMOTE_MASTER HEAD 作为基线: $TEST_BASE_COMMIT"
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# ===== 查找上一个生产标签 =====
|
|
155
|
+
LAST_PROD_TAG=$(git ls-remote --tags "$REMOTE_NAME" | awk -v pfx="refs/tags/$PROD_TAG_PREFIX" '$2 ~ pfx {print $2}' | sed 's#refs/tags/##' | sort | tail -n1 || true)
|
|
156
|
+
echo "ℹ️ 上一个生产标签: ${LAST_PROD_TAG:-<无>}"
|
|
157
|
+
|
|
158
|
+
# ===== 临时分支合并测试基线到生产 =====
|
|
159
|
+
# 检查远程 release 分支是否存在
|
|
160
|
+
if git ls-remote --heads "$REMOTE_NAME" "$REMOTE_RELEASE" | grep -q .; then
|
|
161
|
+
echo "📥 远程生产分支存在,基于远程分支创建临时分支..."
|
|
162
|
+
# 基于远程 release 分支创建临时分支
|
|
163
|
+
git checkout -B "$TMP" "$REMOTE_NAME/$REMOTE_RELEASE"
|
|
164
|
+
else
|
|
165
|
+
echo "📥 远程生产分支不存在,基于当前分支创建..."
|
|
166
|
+
git checkout -B "$TMP"
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
set +e
|
|
170
|
+
git merge --no-ff --no-edit "$TEST_BASE_COMMIT"
|
|
171
|
+
MERGE_STATUS=$?
|
|
172
|
+
set -e
|
|
173
|
+
if [ $MERGE_STATUS -ne 0 ]; then
|
|
174
|
+
echo "❌ 合并冲突,请手动解决后再运行(保留在 $TMP)"
|
|
175
|
+
exit 1
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
# ===== 生成标签 & 变更日志 =====
|
|
179
|
+
SHORTSHA=$(git rev-parse --short "$TEST_BASE_COMMIT")
|
|
180
|
+
NEW_PROD_TAG="${PROD_TAG_PREFIX}$(date +%Y%m%d-%H%M)-${SHORTSHA}"
|
|
181
|
+
CHANGELOG_FILE=".promote_changelog_${NEW_PROD_TAG}.txt"
|
|
182
|
+
if [ -n "$LAST_PROD_TAG" ]; then
|
|
183
|
+
git log --pretty=format:'- %h %s (%an, %ad)' --date=short "${LAST_PROD_TAG}..$TEST_BASE_COMMIT" > "$CHANGELOG_FILE"
|
|
184
|
+
else
|
|
185
|
+
git log --pretty=format:'- %h %s (%an, %ad)' --date=short "$TEST_BASE_COMMIT" > "$CHANGELOG_FILE"
|
|
186
|
+
fi
|
|
187
|
+
echo "📝 变更日志写入: $CHANGELOG_FILE"
|
|
188
|
+
|
|
189
|
+
# ===== 推送到生产分支 + 打标签 =====
|
|
190
|
+
echo "🚀 推送到生产分支: $REMOTE_RELEASE"
|
|
191
|
+
git push "$REMOTE_NAME" "$TMP:$REMOTE_RELEASE"
|
|
192
|
+
|
|
193
|
+
echo "🏷️ 推送生产标签: $NEW_PROD_TAG"
|
|
194
|
+
git tag -a "$NEW_PROD_TAG" -m "Promote $REMOTE_MASTER → $REMOTE_RELEASE
|
|
195
|
+
Base: $TEST_BASE_COMMIT
|
|
196
|
+
Changelog:
|
|
197
|
+
$(cat "$CHANGELOG_FILE")"
|
|
198
|
+
git push "$REMOTE_NAME" "$NEW_PROD_TAG"
|
|
199
|
+
|
|
200
|
+
# ===== 清理现场(cleanup 函数会自动处理,这里显式清理以确保及时释放) =====
|
|
201
|
+
if [ "$(git rev-parse --abbrev-ref HEAD)" = "$TMP" ]; then
|
|
202
|
+
git checkout "$PROJECT_BRANCH" >/dev/null 2>&1 || true
|
|
203
|
+
fi
|
|
204
|
+
if git show-ref --verify --quiet "refs/heads/$TMP" 2>/dev/null; then
|
|
205
|
+
git branch -D "$TMP" >/dev/null 2>&1 || true
|
|
206
|
+
fi
|
|
207
|
+
rm -f "$CHANGELOG_FILE" || true
|
|
208
|
+
|
|
209
|
+
echo "✅ 发布完成!新生产标签: $NEW_PROD_TAG"
|
|
210
|
+
echo "ℹ️ 回滚提示:可回滚到上一个生产标签:${LAST_PROD_TAG:-<无>}"
|