1
1
name : Update from Template
2
2
3
3
# This workflow keeps the repo up to date with changes from the template repo (REMOTE_URL)
4
- # It duplicates the REMOTE_BRANCH (into UPDATE_BRANCH) and tries to merge it into the
4
+ # It duplicates the REMOTE_BRANCH (into UPDATE_BRANCH) and tries to merge it into
5
5
# this repos default branch (which is checked out here)
6
6
# Note that this requires a PAT (Personal Access Token) - at best from a servicing account
7
+ # PAT permissions: read:discussion, read:org, repo, workflow
7
8
# Also note that you should have at least once merged the template repo into the current repo manually
8
9
# otherwise a "refusing to merge unrelated histories" error might occur.
9
10
10
11
on :
11
12
schedule :
12
13
- cron : ' 55 2 * * 1'
13
14
workflow_dispatch :
15
+ inputs :
16
+ no_automatic_merge :
17
+ type : boolean
18
+ description : ' No automatic merge'
19
+ default : false
14
20
15
21
env :
16
22
UPDATE_BRANCH : update-from-template
23
+ UPDATE_BRANCH_MERGED : update-from-template-merged
17
24
REMOTE_URL : https://github.com/xdev-software/intellij-plugin-template.git
18
25
REMOTE_BRANCH : master
19
26
@@ -24,7 +31,9 @@ permissions:
24
31
jobs :
25
32
update :
26
33
runs-on : ubuntu-latest
27
-
34
+ outputs :
35
+ update_branch_merged_commit : ${{ steps.manage-branches.outputs.update_branch_merged_commit }}
36
+ create_update_branch_merged_pr : ${{ steps.manage-branches.outputs.create_update_branch_merged_pr }}
28
37
steps :
29
38
- uses : actions/checkout@v4
30
39
with :
@@ -36,31 +45,34 @@ jobs:
36
45
37
46
- name : Init Git
38
47
run : |
39
- git config --global user.email "actions@ github.com"
40
- git config --global user.name "GitHub Actions "
48
+ git config --global user.email "[email protected] . github.com"
49
+ git config --global user.name "XDEV Bot "
41
50
42
- - name : Main workflow
43
- id : main
51
+ - name : Manage branches
52
+ id : manage-branches
44
53
run : |
45
54
echo "Adding remote template-repo"
46
55
git remote add template ${{ env.REMOTE_URL }}
47
56
48
57
echo "Fetching remote template repo"
49
58
git fetch template
50
59
51
- echo "Deleting local branch that will contain the updates - if present"
60
+ echo "Deleting local branches that will contain the updates - if present"
52
61
git branch -D ${{ env.UPDATE_BRANCH }} || true
62
+ git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true
53
63
54
64
echo "Checking if the remote template repo has new commits"
55
65
git rev-list ..template/${{ env.REMOTE_BRANCH }}
56
66
57
67
if [ $(git rev-list --count ..template/${{ env.REMOTE_BRANCH }}) -eq 0 ]; then
58
68
echo "There are no commits new commits on the template repo"
59
69
60
- echo "Deleting origin branch that contains the updates - if present"
70
+ echo "Deleting origin branch(es) that contain the updates - if present"
61
71
git push -f origin --delete ${{ env.UPDATE_BRANCH }} || true
72
+ git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true
62
73
63
- echo "abort=1" >> $GITHUB_OUTPUT
74
+ echo "create_update_branch_pr=0" >> $GITHUB_OUTPUT
75
+ echo "create_update_branch_merged_pr=0" >> $GITHUB_OUTPUT
64
76
exit 0
65
77
fi
66
78
@@ -73,21 +85,230 @@ jobs:
73
85
echo "Pushing update branch"
74
86
git push -f -u origin ${{ env.UPDATE_BRANCH }}
75
87
76
- echo "Getting current branch"
77
- current_branch =$(git branch --show-current)
78
- echo "Current branch is $current_branch "
79
- echo "current_branch=$current_branch " >> $GITHUB_OUTPUT
88
+ echo "Getting base branch"
89
+ base_branch =$(git branch --show-current)
90
+ echo "Base branch is $base_branch "
91
+ echo "base_branch=$base_branch " >> $GITHUB_OUTPUT
80
92
81
- echo "abort=0" >> $GITHUB_OUTPUT
93
+ echo "Trying to create auto-merged branch ${{ env.UPDATE_BRANCH_MERGED }}"
94
+ git branch ${{ env.UPDATE_BRANCH_MERGED }} ${{ env.UPDATE_BRANCH }}
95
+ git checkout ${{ env.UPDATE_BRANCH_MERGED }}
82
96
83
- - name : pull-request
84
- if : steps.main.outputs.abort == 0
97
+ echo "Merging branch $base_branch into ${{ env.UPDATE_BRANCH_MERGED }}"
98
+ git merge $base_branch && merge_exit_code=$? || merge_exit_code=$?
99
+ if [ $merge_exit_code -ne 0 ]; then
100
+ echo "Auto merge failed! Manual merge required"
101
+ echo "::notice ::Auto merge failed - Manual merge required"
102
+
103
+ echo "Cleaning up failed merge"
104
+ git merge --abort
105
+ git checkout $base_branch
106
+ git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true
107
+
108
+ echo "Deleting auto-merge branch - if present"
109
+ git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true
110
+
111
+ echo "create_update_branch_pr=1" >> $GITHUB_OUTPUT
112
+ echo "create_update_branch_merged_pr=0" >> $GITHUB_OUTPUT
113
+ exit 0
114
+ fi
115
+
116
+ echo "Post processing: Trying to automatically fill in template variables"
117
+ find . -type f \
118
+ -not -path "./.git/**" \
119
+ -not -path "./.github/workflows/update-from-template.yml" -print0 \
120
+ | xargs -0 sed -i "s/template-placeholder/${GITHUB_REPOSITORY#*/}/g"
121
+
122
+ git status
123
+ git add --all
124
+
125
+ if [[ "$(git status --porcelain)" != "" ]]; then
126
+ echo "Filled in template; Committing"
127
+
128
+ git commit -m "Fill in template"
129
+ fi
130
+
131
+ echo "Pushing auto-merged branch"
132
+ git push -f -u origin ${{ env.UPDATE_BRANCH_MERGED }}
133
+
134
+ echo "update_branch_merged_commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
135
+
136
+ echo "Restoring base branch $base_branch"
137
+ git checkout $base_branch
138
+
139
+ echo "create_update_branch_pr=0" >> $GITHUB_OUTPUT
140
+ echo "create_update_branch_merged_pr=1" >> $GITHUB_OUTPUT
141
+ echo "try_close_update_branch_pr=1" >> $GITHUB_OUTPUT
142
+
143
+ - name : Create/Update PR update_branch
144
+ if : steps.manage-branches.outputs.create_update_branch_pr == 1
85
145
env :
86
- GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
146
+ GH_TOKEN : ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }}
87
147
run : |
88
148
gh_pr_up() {
89
149
gh pr create -H "${{ env.UPDATE_BRANCH }}" "$@" || (git checkout "${{ env.UPDATE_BRANCH }}" && gh pr edit "$@")
90
150
}
91
- gh_pr_up -B "${{ steps.main .outputs.current_branch }}" \
151
+ gh_pr_up -B "${{ steps.manage-branches .outputs.base_branch }}" \
92
152
--title "Update from template" \
93
153
--body "An automated PR to sync changes from the template into this repo"
154
+
155
+ # Ensure that only a single PR is open (otherwise confusion and spam)
156
+ - name : Close PR update_branch
157
+ if : steps.manage-branches.outputs.try_close_update_branch_pr == 1
158
+ env :
159
+ GH_TOKEN : ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }}
160
+ run : |
161
+ gh pr close "${{ env.UPDATE_BRANCH }}" || true
162
+
163
+ - name : Create/Update PR update_branch_merged
164
+ if : steps.manage-branches.outputs.create_update_branch_merged_pr == 1
165
+ env :
166
+ GH_TOKEN : ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }}
167
+ run : |
168
+ gh_pr_up() {
169
+ gh pr create -H "${{ env.UPDATE_BRANCH_MERGED }}" "$@" || (git checkout "${{ env.UPDATE_BRANCH_MERGED }}" && gh pr edit "$@")
170
+ }
171
+ gh_pr_up -B "${{ steps.manage-branches.outputs.base_branch }}" \
172
+ --title "Update from template (auto-merged)" \
173
+ --body "An automated PR to sync changes from the template into this repo"
174
+
175
+ # Wait a moment so that checks of PR have higher prio than following job
176
+ sleep 3
177
+
178
+ # Split into two jobs to help with executor starvation
179
+ auto-merge :
180
+ needs : [update]
181
+ if : needs.update.outputs.create_update_branch_merged_pr == 1
182
+ runs-on : ubuntu-latest
183
+ steps :
184
+ - uses : actions/checkout@v4
185
+ with :
186
+ # Required because otherwise there are always changes detected when executing diff/rev-list
187
+ fetch-depth : 0
188
+ # If no PAT is used the following error occurs on a push:
189
+ # refusing to allow a GitHub App to create or update workflow `.github/workflows/xxx.yml` without `workflows` permission
190
+ token : ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }}
191
+
192
+ - name : Init Git
193
+ run : |
194
+ git config --global user.email "[email protected] "
195
+ git config --global user.name "XDEV Bot"
196
+
197
+ - name : Checking if auto-merge for PR update_branch_merged can be done
198
+ id : auto-merge-check
199
+ env :
200
+ GH_TOKEN : ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }}
201
+ run : |
202
+ not_failed_conclusion="skipped|neutral|success"
203
+ not_relevant_app_slug="dependabot|github-pages|sonarcloud"
204
+
205
+ echo "Waiting for checks to start..."
206
+ sleep 40s
207
+
208
+ for i in {1..20}; do
209
+ echo "Checking if PR can be auto-merged. Try: $i"
210
+
211
+ echo "Checking if update-branch-merged exists"
212
+ git fetch
213
+ if [[ $(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }}) ]]; then
214
+ echo "Branch origin/${{ env.UPDATE_BRANCH_MERGED }} is missing"
215
+ exit 0
216
+ fi
217
+
218
+ echo "Fetching checks"
219
+ cs_response=$(curl -sL \
220
+ --fail-with-body \
221
+ --connect-timeout 60 \
222
+ --max-time 120 \
223
+ -H "Accept: application/vnd.github+json" \
224
+ -H "Authorization: Bearer $GH_TOKEN" \
225
+ -H "X-GitHub-Api-Version: 2022-11-28" \
226
+ https://api.github.com/repos/${{ github.repository }}/commits/${{ needs.update.outputs.update_branch_merged_commit }}/check-suites)
227
+
228
+ cs_data=$(echo $cs_response | jq '.check_suites[] | { conclusion: .conclusion, slug: .app.slug, check_runs_url: .check_runs_url }')
229
+ echo $cs_data
230
+
231
+ if [[ -z "$cs_data" ]]; then
232
+ echo "No check suite data - Assuming that there are no checks to run"
233
+
234
+ echo "perform=1" >> $GITHUB_OUTPUT
235
+ exit 0
236
+ fi
237
+
238
+ cs_failed=$(echo $cs_data | jq --arg x "$not_failed_conclusion" 'select ((.conclusion == null or (.conclusion | test($x))) | not)')
239
+ if [[ -z "$cs_failed" ]]; then
240
+ echo "No check failed so far; Checking if relevant checks are still running"
241
+
242
+ cs_relevant_still_running=$(echo $cs_data | jq --arg x "$not_relevant_app_slug" 'select (.conclusion == null and (.slug | test($x) | not))')
243
+ if [[ -z $cs_relevant_still_running ]]; then
244
+ echo "All relevant checks finished - PR can be merged"
245
+
246
+ echo "perform=1" >> $GITHUB_OUTPUT
247
+ exit 0
248
+ else
249
+ echo "Relevant checks are still running"
250
+ echo $cs_relevant_still_running
251
+ fi
252
+ else
253
+ echo "Detected failed check"
254
+ echo $cs_failed
255
+
256
+ echo "perform=0" >> $GITHUB_OUTPUT
257
+ exit 0
258
+ fi
259
+
260
+ echo "Waiting before next run..."
261
+ sleep 30s
262
+ done
263
+
264
+ echo "Timed out - Assuming executor starvation - Forcing merge"
265
+ echo "perform=1" >> $GITHUB_OUTPUT
266
+
267
+ - name : Auto-merge update_branch_merged
268
+ if : steps.auto-merge-check.outputs.perform == 1
269
+ run : |
270
+ echo "Getting base branch"
271
+ base_branch=$(git branch --show-current)
272
+ echo "Base branch is $base_branch"
273
+
274
+ echo "Fetching..."
275
+ git fetch
276
+ if [[ $(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }}) ]]; then
277
+ echo "Branch origin/${{ env.UPDATE_BRANCH_MERGED }} is missing"
278
+ exit 0
279
+ fi
280
+
281
+ expected_commit="${{ needs.update.outputs.update_branch_merged_commit }}"
282
+ actual_commit=$(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }})
283
+ if [[ "$expected_commit" != "$actual_commit" ]]; then
284
+ echo "Branch ${{ env.UPDATE_BRANCH_MERGED }} contains unexpected commit $actual_commit"
285
+ echo "Expected: $expected_commit"
286
+
287
+ exit 0
288
+ fi
289
+
290
+ echo "Ensuring that current branch $base_branch is up-to-date"
291
+ git pull
292
+
293
+ echo "Merging origin/${{ env.UPDATE_BRANCH_MERGED }} into $base_branch"
294
+ git merge origin/${{ env.UPDATE_BRANCH_MERGED }} && merge_exit_code=$? || merge_exit_code=$?
295
+ if [ $merge_exit_code -ne 0 ]; then
296
+ echo "Unexpected merge failure $merge_exit_code - Requires manual resolution"
297
+
298
+ exit 0
299
+ fi
300
+
301
+ if [[ "${{ inputs.no_automatic_merge }}" == "true" ]]; then
302
+ echo "Exiting due no_automatic_merge"
303
+
304
+ exit 0
305
+ fi
306
+
307
+ echo "Pushing"
308
+ git push
309
+
310
+ echo "Cleaning up"
311
+ git branch -D ${{ env.UPDATE_BRANCH }} || true
312
+ git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true
313
+ git push -f origin --delete ${{ env.UPDATE_BRANCH }} || true
314
+ git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true
0 commit comments