forked from yairm210/Unciv
-
Notifications
You must be signed in to change notification settings - Fork 1
307 lines (260 loc) · 13.9 KB
/
uncivbot.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
name: UncivBot
on:
issue_comment:
workflow_dispatch:
jobs:
update_wiki:
if: github.event_name == 'issue_comment' && github.event.issue.pull_request && github.event.comment.body == 'update wiki' && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)
# This is the only place I could find an apparent list of valid author associations: https://docs.github.com/en/graphql/reference/enums#commentauthorassociation
runs-on: ubuntu-latest
steps:
- name: Update Wiki.
id: update
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.ACTIONS_ACCESS_TOKEN }}
CODE_DIR: CodeRepo
WIKI_DIR: WikiRepo
WIKI_IN_CODE_REPO: /docs/wiki
EVENT_CONTEXT: ${{ toJSON(github) }}
# Dump for debug.
PR_MERGEDAT: ${{ toJSON(github.event.issue.pull_request.merged_at) }}
# The environment variable isn't set for `null`, so stringify.
PR_AUTHOR: ${{ github.event.issue.user.login }}
# I think directly putting these in the script would let quotes in PR title break the script. Set environment variables so Bash can take care of the substitution.
PR_TITLE: ${{ github.event.issue.title }}
PR_NUMBER: ${{ github.event.issue.number }}
PR_URL: ${{ github.event.issue.html_url }}
# Compare to https://github.com/SwiftDocOrg/github-wiki-publish-action/blob/v1/entrypoint.sh
# May also be some value in replacing `echo` for logging with some more structured GH Action commands: https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions
run: |
#echo "$EVENT_CONTEXT" # Dump entire event to logs. Watch out for leaks.
set -euo pipefail # Immediate exit on command errors and unset variables.
#set -x # Print out each executed command. THIS MAY LEAK THE PERSONAL ACCESS TOKEN SECRET VIA $GH_TOKEN and $WIKI_URL (though AFAICT GH is sanitizing it)!
function ghEnvVar() {
GHENV_DELIMITER="#GH-EOF_$(date +%s)-$RANDOM!"
echo "$1<<$GHENV_DELIMITER" >> $GITHUB_ENV
echo "$2" >> $GITHUB_ENV
echo "$GHENV_DELIMITER" >> $GITHUB_ENV
# This sets an environment variable for subsequent GH Action steps.
}
ghEnvVar WIKIBOT_STATUS ""
ghEnvVar WIKIBOT_CHANGES ""
function ghStatus() {
echo $@
ghEnvVar WIKIBOT_STATUS "$@"
}
for REQUIRED_ENV_VAR in GITHUB_ENV GITHUB_ACTOR GITHUB_SERVER_URL GITHUB_REPOSITORY GH_TOKEN CODE_DIR WIKI_DIR WIKI_IN_CODE_REPO PR_MERGEDAT PR_AUTHOR PR_TITLE PR_NUMBER PR_URL; do
if [ -z "$(eval "echo \$$REQUIRED_ENV_VAR")" ]; then
ghStatus "ERROR: \$$REQUIRED_ENV_VAR is not set."
exit 1
fi
done
if [ "${PR_MERGEDAT}" = 'null' ]; then
ghStatus "ERROR: Pull request must be merged."
exit 1
fi
CODE_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
WIKI_URL="https://${GH_TOKEN}@${GITHUB_SERVER_URL#https://}/${GITHUB_REPOSITORY}.wiki.git"
ghStatus "Cloning repository: $CODE_URL"
git clone "$CODE_URL" "$CODE_DIR"
ghStatus "Cloning Wiki."
git clone "$WIKI_URL" "$WIKI_DIR"
ghStatus 'Running `rsync`.'
rsync -avc --delete --exclude '.git*' "${CODE_DIR}${WIKI_IN_CODE_REPO}/" "$WIKI_DIR/"
# …The trailing slashes here are very important! Otherwise it will copy/target the directory, instead of its contents.
ghStatus "Formatting Markdown files in $WIKI_DIR."
cd "$WIKI_DIR"
for f in *.md; do
if [ -e "$f" ]; then
ghStatus "Formatting $f links."
# Convert AS/GH code browser inter-page links to GH Wiki links by stripping `.md` extensions.
sed -ie 's|\(](\./[^)]*\)\.md|\1|g' "$f"
# Convert AS/GH code browser repo file links to GH Wiki links by prepending repo browser to absolute links.
sed -ie 's|](/|](https://github.com/'"${GITHUB_REPOSITORY}"'/tree/master/|g' "$f"
else # When glob produces no matches, you just get the glob pattern instead.
ghStatus "Skipping non-existent file $f."
fi
done
ghStatus "Finished formatting Markdown files."
git config user.name "$GITHUB_ACTOR"
git config user.email "[email protected]"
ghStatus "Summarizing files."
tree
ghStatus "Adding files to Git."
git add .
ghStatus "Summarizing diff."
CHANGES="$(git diff --stat --cached | cat)" # IDK If Git might try to enter the interactive viewer without a pipe output. Probably not, but play it safe.
echo "$CHANGES"
ghEnvVar WIKIBOT_CHANGES "$CHANGES"
ghStatus "Committing changes."
git commit --allow-empty -m "@${PR_AUTHOR}: ${PR_TITLE} (#${PR_NUMBER})" -m "${PR_URL}"
ghStatus "Pushing changes."
git push # Since we just cloned the wiki repo anyway, it should also be safe to use -f.
#git push --set-upstream "$WIKI_URL" master # Should be the same since we cloned from the URL, just more explicit.
ghStatus "Done."
- name: Report Wiki update status.
id: report
continue-on-error: false
uses: actions/github-script@v3
env:
WIKIBOT_OUTCOME: ${{ steps.update.outcome }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const message = []
message.push(`Wiki update: ${process.env.WIKIBOT_OUTCOME}.`)
message.push("")
message.push("Status:")
message.push("```")
message.push(process.env.WIKIBOT_STATUS)
message.push("```")
message.push("")
message.push("Changes:")
message.push("```")
message.push(process.env.WIKIBOT_CHANGES)
message.push("```")
//message.push("\n<details><summary>Details</summary>\n\n```JSON\n" + JSON.stringify({context: context, github: github, core: core, glob: glob, io: io, process: process}, null, '\t') + '\n```\n</details>\n')
// Uncomment this for debug. But delete the bot comments on GH afterwards in case it leaks anything.
github.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: message.join("\n")
})
if (process.env.WIKIBOT_OUTCOME != 'success') {
core.setFailed(`Failed at: ${process.env.WIKIBOT_STATUS}.`)
}
summary:
if: github.event_name == 'issue_comment' && github.event.comment.body == 'summary' && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)
# This is the only place I could find an apparent list of valid author associations. Also, at least they're not case-sensitive: https://docs.github.com/en/graphql/reference/enums#commentauthorassociation https://docs.github.com/en/actions/learn-github-actions/expressions#contains
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
var result = await github.repos.listCommits({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 50 });
var commitSummary = "";
var ownerToCommits = {}
var reachedPreviousVersion = false
result.data.forEach(commit => {
if (reachedPreviousVersion) return
var author = commit.author.login
if (author=="uncivbot[bot]") return
var commitMessage = commit.commit.message.split("\n")[0];
if (commitMessage.match(/^\d+\.\d+\.\d+$/)){ // match EXACT version, like 3.4.55 ^ is for start-of-line, $ for end-of-line
reachedPreviousVersion=true
console.log(commitMessage)
return
}
if (commitMessage.startsWith("Merge ") || commitMessage.startsWith("Update ")) return
commitMessage = commitMessage.replace(/\(\#\d+\)/,"") // match PR auto-text, like (#2345)
if (author != context.repo.owner){
if (ownerToCommits[author] == undefined) ownerToCommits[author]=[]
ownerToCommits[author].push(commitMessage)
}
else commitSummary += "\n\n" + commitMessage
});
Object.entries(ownerToCommits).forEach(entry => {
const [author, commits] = entry;
if (commits.length==1) commitSummary += "\n\n" + commits[0] + " - By "+author
else {
commitSummary += "\n\nBy "+author+":"
commits.forEach(commitMessage => { commitSummary += "\n- "+commitMessage })
}
})
github.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: commitSummary
})
merge_translations:
if: github.event_name == 'workflow_dispatch' || (github.event.comment.body == 'merge translations' && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association))
# This is the only place I could find an apparent list of valid author associations. Also, at least they're not case-sensitive: https://docs.github.com/en/graphql/reference/enums#commentauthorassociation https://docs.github.com/en/actions/learn-github-actions/expressions#contains
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v3
with:
# SO, the story is that when using the default access token you CANNOT merge PRs from forks.
# _Badly_ documented in multiple places, including here: https://docs.github.com/en/actions/reference/authentication-in-a-workflow
# To get around this, we created a Personal Access Token,
# put it as one of the secrets in the repo settings (https://github.com/yairm210/Unciv/settings/secrets/actions),
# and use that instead.
github-token: ${{ secrets.ACTIONS_ACCESS_TOKEN }}
script: |
const repo = {
owner: context.repo.owner,
repo: context.repo.repo }
async function branchExists(branchName) {
try {
await github.git.getRef({...repo, ref: 'heads/' + branchName })
return true
} catch (err) {
return false
}
}
async function getDefaultBranch() {
var repoData = await github.repos.get(repo)
return repoData.data.default_branch
}
var translations = "translations"
async function createTranslationBranchIfNeeded() {
if (await branchExists(translations)) return
var defaultBranch = await getDefaultBranch()
var currentHead = await github.git.getRef({...repo, ref: 'heads/' + defaultBranch })
var currentSha = currentHead.data.object.sha
console.log("Current sha: " + currentSha)
await github.git.createRef({...repo,
ref: `refs/heads/`+translations,
sha: currentSha })
await github.issues.createComment({...repo,
issue_number: context.issue.number,
body: 'Translations branch created' })
}
async function mergeExistingTranslationsIntoBranch(){
var translationPrs = await github.pulls.list({ ...repo, state: "open" })
// When we used a forEach loop here, only one merge would happen at each run,
// because we essentially started multiple async tasks in parallel and they conflicted.
// Instead, we use X of Y as per https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop
for (const pr of translationPrs.data) {
if (pr.labels.some(label => label.name == "mergeable translation"))
await tryMergePr(pr)
}
}
async function tryMergePr(pr){
if (pr.base.ref != translations)
await github.pulls.update({ ...repo,
pull_number: pr.number,
base: translations })
try {
await github.pulls.merge({...repo,
pull_number: pr.number,
merge_method: "squash" })
console.log("Merged #"+pr.number+", "+pr.title)
} catch (err) {
console.log(err)
}
}
async function createTranslationPrIfNeeded() {
var translationPulls = await github.pulls.list({...repo,
state: "open",
head: context.repo.owner + ":" + translations });
if (translationPulls.data.length == 0) {
var defaultBranch = await getDefaultBranch(context);
await github.pulls.create({...repo,
title: "Translations update",
head: translations,
base: defaultBranch });
await github.issues.createComment({...repo,
issue_number: context.issue.number,
body: 'Translations PR created' });
}
}
await createTranslationBranchIfNeeded()
await mergeExistingTranslationsIntoBranch()
await createTranslationPrIfNeeded()