Остання активність 16 hours ago

AppStore完善同步更新Action脚本,支持强制同步,自动合入,解决冲突

LiuShen's Avatar LiuShen ревизій цього gist 16 hours ago. До ревизії

2 files changed, 312 insertions

renovate-app-version.yml(файл створено)

@@ -0,0 +1,212 @@
1 + name: Update app version in Renovate Branches
2 +
3 + on:
4 + push:
5 + branches: [ 'renovate/*' ]
6 + workflow_dispatch:
7 + inputs:
8 + manual-trigger:
9 + description: 'Manually trigger Renovate'
10 + default: ''
11 +
12 + # 并发控制:确保同一时间只有一个 PR 合并操作在进行,避免竞争条件
13 + concurrency:
14 + group: merge-renovate-pr
15 + cancel-in-progress: false
16 +
17 + jobs:
18 + update-app-version:
19 + runs-on: ubuntu-latest
20 + steps:
21 + - name: Checkout
22 + uses: actions/checkout@v5
23 + with:
24 + fetch-depth: 0
25 +
26 + - name: Check if triggered by self (prevent circular trigger)
27 + id: check-circular
28 + run: |
29 + # 检查最后一个 commit 的 author,如果是自己则跳过
30 + last_author=$(git log -1 --format='%an')
31 + last_message=$(git log -1 --format='%s')
32 +
33 + echo "Last commit author: $last_author"
34 + echo "Last commit message: $last_message"
35 +
36 + # 如果是 github-action 自己 commit 的,说明是工作流触发的,跳过避免循环
37 + if [[ "$last_author" == "github-action update-app-version" ]] || \
38 + [[ "$last_author" == "github-action[bot]" ]] || \
39 + [[ "$last_message" == *"[auto-version-bump]"* ]]; then
40 + echo "⚠️ Skipping: triggered by own commit (preventing circular trigger)"
41 + echo "skip=true" >> $GITHUB_OUTPUT
42 + else
43 + echo "✅ Proceeding: triggered by external commit"
44 + echo "skip=false" >> $GITHUB_OUTPUT
45 + fi
46 +
47 + - name: Configure git
48 + if: steps.check-circular.outputs.skip != 'true'
49 + run: |
50 + git config --local user.email "githubaction@githubaction.com"
51 + git config --local user.name "github-action update-app-version"
52 +
53 + - name: Get list of updated files by the last commit
54 + if: steps.check-circular.outputs.skip != 'true'
55 + id: updated-files
56 + run: |
57 + echo "files=$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | tr '\n' ' ')" >> $GITHUB_OUTPUT
58 +
59 + - name: Run renovate-app-version.sh on updated files
60 + if: steps.check-circular.outputs.skip != 'true'
61 + id: rename
62 + run: |
63 + set -e
64 + chmod +x .github/workflows/renovate-app-version.sh
65 +
66 + files="${{ steps.updated-files.outputs.files }}"
67 + declare -a changed_apps=()
68 +
69 + echo "Updated files: $files"
70 +
71 + for file in $files; do
72 + if [[ $file == *"docker-compose.yml"* ]]; then
73 + echo "Processing file: $file"
74 +
75 + app_name=$(echo $file | cut -d'/' -f 2)
76 + old_version=$(echo $file | cut -d'/' -f 3)
77 + echo "App name: $app_name, old version: $old_version"
78 +
79 + # 获取所有服务名
80 + services=$(yq '.services | keys | .[]' "$file")
81 + service=""
82 + image_line=""
83 +
84 + for s in $services; do
85 + # 通过awk获取服务下的image行(包含注释)
86 + image_line=$(awk "/services:/{flag=0} /^\s*$s:/{flag=1} flag && /^\s*image:/{print; exit}" "$file")
87 + echo "Service $s image line: $image_line"
88 + if [[ "$image_line" != *"[ignore]"* ]]; then
89 + service="$s"
90 + break
91 + else
92 + echo "Skipping service $s due to [ignore]"
93 + fi
94 + done
95 +
96 + if [[ -z "$service" ]]; then
97 + echo "No valid service found in $file, skipping..."
98 + continue
99 + fi
100 +
101 + # 提取image纯字符串,去除注释和多余空格
102 + image=$(echo "$image_line" | sed -E 's/^\s*image:\s*([^ #]+).*/\1/')
103 + echo "Selected service: $service"
104 + echo "Extracted image: $image"
105 +
106 + if [[ "$image" == *":"* ]]; then
107 + new_version=$(cut -d ":" -f2- <<< "$image")
108 + trimmed_version=${new_version/#"v"/}
109 + echo "Parsed new version: $trimmed_version"
110 + else
111 + trimmed_version=""
112 + echo "No version tag found in image."
113 + fi
114 +
115 + changed_apps+=("${app_name}:${old_version}:${trimmed_version}")
116 + echo "Calling renovate-app-version.sh with: $app_name, $old_version, $trimmed_version"
117 + .github/workflows/renovate-app-version.sh "$app_name" "$old_version" "$trimmed_version"
118 + fi
119 + done
120 +
121 + echo "All changed apps: ${changed_apps[*]}"
122 + echo "apps=$(IFS=, ; echo "${changed_apps[*]}")" >> $GITHUB_OUTPUT
123 +
124 + - name: Commit & Push Changes
125 + if: steps.check-circular.outputs.skip != 'true'
126 + run: |
127 + set -e
128 + IFS=',' read -r -a apps <<< "${{ steps.rename.outputs.apps }}"
129 + for item in "${apps[@]}"; do
130 + app_name=$(cut -d':' -f1 <<< "$item")
131 + old_version=$(cut -d':' -f2 <<< "$item")
132 + new_version=$(cut -d':' -f3 <<< "$item")
133 +
134 + if [[ -n "$app_name" && -n "$new_version" ]]; then
135 + git add "apps/$app_name/*"
136 + git commit -m "📈将应用 $app_name 的版本从 $old_version 升级到 $new_version" --no-verify || echo "无内容可提交"
137 + fi
138 + done
139 +
140 + git push || echo "无内容可推送"
141 +
142 + - name: Rebase to latest main and merge PR
143 + if: steps.check-circular.outputs.skip != 'true' && github.ref_name != 'main'
144 + env:
145 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
146 + run: |
147 + set -e
148 + branch_name=$(git rev-parse --abbrev-ref HEAD)
149 + echo "Current branch: $branch_name"
150 +
151 + # 获取 PR 编号
152 + pr_number=$(gh pr list --state open --head "$branch_name" --json number -q '.[0].number')
153 + if [ -z "$pr_number" ]; then
154 + echo "No PR found for branch $branch_name"
155 + exit 0
156 + fi
157 +
158 + echo "Found PR #$pr_number, preparing to merge..."
159 +
160 + # Fetch 最新的 main 分支
161 + echo "Fetching latest main branch..."
162 + git fetch origin main
163 +
164 + # Rebase 当前分支到最新的 main
165 + # 由于 renovate PR 修改的是不同的应用文件夹,不会有真正的冲突
166 + echo "Rebasing $branch_name onto origin/main..."
167 + git rebase origin/main
168 +
169 + # Force push 更新后的分支
170 + echo "Force pushing rebased branch..."
171 + git push --force-with-lease
172 +
173 + # 等待 GitHub 重新计算 mergeable 状态
174 + echo "Waiting for GitHub to recalculate mergeable status..."
175 + sleep 15
176 +
177 + # 检查 mergeable 状态,最多等待 2 分钟
178 + max_attempts=12
179 + attempt=0
180 + while [ $attempt -lt $max_attempts ]; do
181 + mergeable=$(gh pr view "$pr_number" --json mergeable -q '.mergeable' 2>/dev/null || echo "UNKNOWN")
182 + echo "Mergeable status: $mergeable (attempt $((attempt+1))/$max_attempts)"
183 +
184 + if [ "$mergeable" = "MERGEABLE" ]; then
185 + echo "PR is mergeable!"
186 + break
187 + fi
188 +
189 + # 如果是 CONFLICTING,说明有真正的冲突(不应该发生)
190 + if [ "$mergeable" = "CONFLICTING" ]; then
191 + echo "Warning: PR has conflicts, but proceeding with admin merge..."
192 + break
193 + fi
194 +
195 + sleep 10
196 + attempt=$((attempt+1))
197 + done
198 +
199 + # 合并 PR
200 + echo "Merging PR #$pr_number..."
201 + gh pr merge "$pr_number" --merge --delete-branch --admin
202 +
203 + echo "Successfully merged PR #$pr_number!"
204 +
205 + - name: Trigger sync to CNB mirror
206 + if: steps.check-circular.outputs.skip != 'true' && github.ref_name != 'main'
207 + env:
208 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
209 + run: |
210 + echo "Triggering sync-to-cnb workflow..."
211 + gh workflow run sync-to-cnb.yml --ref main
212 + echo "Sync workflow triggered successfully!"

sync-to-cnb.yml(файл створено)

@@ -0,0 +1,100 @@
1 + name: Sync commits to CNB (force)
2 +
3 + on:
4 + schedule:
5 + - cron: "0 0 * * *" # 每天凌晨 0 点强制同步(兜底)
6 + - cron: "0 */6 * * *" # 每 6 小时检查一次
7 + push:
8 + branches:
9 + - main
10 + workflow_call: # 允许被其他工作流调用
11 + workflow_dispatch:
12 + inputs:
13 + reason:
14 + description: 'Manual trigger reason'
15 + required: false
16 + default: 'Manual run'
17 + force_sync:
18 + description: 'Force sync without checking for new commits'
19 + required: false
20 + default: 'false'
21 +
22 + jobs:
23 + sync:
24 + runs-on: ubuntu-latest
25 + outputs:
26 + synced: ${{ steps.sync.outputs.synced }}
27 +
28 + steps:
29 + - name: Checkout GitHub repository
30 + uses: actions/checkout@v5
31 + with:
32 + ref: main
33 + fetch-depth: 0
34 +
35 + - name: Determine sync mode
36 + id: mode
37 + run: |
38 + # 每天凌晨的定时任务强制同步
39 + if [[ "${{ github.event.schedule }}" == "0 0 * * *" ]]; then
40 + echo "Daily scheduled sync - forcing sync"
41 + echo "force=true" >> $GITHUB_OUTPUT
42 + exit 0
43 + fi
44 +
45 + # 手动触发且指定强制同步
46 + if [[ "${{ github.event.inputs.force_sync }}" == "true" ]]; then
47 + echo "Manual force sync requested"
48 + echo "force=true" >> $GITHUB_OUTPUT
49 + exit 0
50 + fi
51 +
52 + # 被 workflow_call 调用时强制同步
53 + if [[ "${{ github.event_name }}" == "workflow_call" ]]; then
54 + echo "Called by another workflow - forcing sync"
55 + echo "force=true" >> $GITHUB_OUTPUT
56 + exit 0
57 + fi
58 +
59 + echo "force=false" >> $GITHUB_OUTPUT
60 +
61 + - name: Check if there are new commits
62 + if: steps.mode.outputs.force != 'true'
63 + id: check
64 + run: |
65 + NEW_COMMITS=$(git log --since="6 hours ago" --oneline)
66 + if [ -z "$NEW_COMMITS" ]; then
67 + echo "No new commits in the last 6 hours, skip sync."
68 + echo "skip=true" >> $GITHUB_OUTPUT
69 + else
70 + echo "New commits found:"
71 + echo "$NEW_COMMITS"
72 + echo "skip=false" >> $GITHUB_OUTPUT
73 + fi
74 +
75 + - name: Sync to CNB
76 + id: sync
77 + if: steps.mode.outputs.force == 'true' || steps.check.outputs.skip == 'false'
78 + env:
79 + CNB_USERNAME: ${{ secrets.CNB_USERNAME }}
80 + CNB_TOKEN: ${{ secrets.CNB_TOKEN }}
81 + run: |
82 + echo "Syncing to CNB mirror..."
83 + rm -rf .git
84 + git init
85 + git config --global user.name 'GitHub Action'
86 + git config --global user.email 'action@github.com'
87 + git add .
88 + git commit -m "Sync commit ==> [$(date +"%Y-%m-%d %H:%M:%S")]"
89 + git branch -M main
90 + git remote add origin "https://${{ secrets.CNB_USERNAME }}:${{ secrets.CNB_TOKEN }}@cnb.cool/liiiu/appstore.git"
91 + git push --force --quiet origin main
92 + echo "synced=true" >> $GITHUB_OUTPUT
93 +
94 + - name: Report result
95 + run: |
96 + if [[ "${{ steps.sync.outputs.synced }}" == "true" ]]; then
97 + echo "✅ Successfully synced to CNB mirror"
98 + else
99 + echo "⏭️ Skipped sync (no new commits)"
100 + fi
Новіше Пізніше