最后活跃于 14 hours ago

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

renovate-app-version.yml 原始文件
1name: Update app version in Renovate Branches
2
3on:
4 push:
5 branches: [ 'renovate/*' ]
6 workflow_dispatch:
7 inputs:
8 manual-trigger:
9 description: 'Manually trigger Renovate'
10 default: ''
11
12# 并发控制:确保同一时间只有一个 PR 合并操作在进行,避免竞争条件
13concurrency:
14 group: merge-renovate-pr
15 cancel-in-progress: false
16
17jobs:
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 原始文件
1name: Sync commits to CNB (force)
2
3on:
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
22jobs:
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
101