-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathrole-make-version-changelog.sh
executable file
·390 lines (373 loc) · 14.2 KB
/
role-make-version-changelog.sh
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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
#!/bin/bash
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Red Hat, Inc.
# SPDX-License-Identifier: MIT
set -euo pipefail
# By default, this script will skip doing a release of roles
# that do not appear to have any changes since the last release.
# If you really want to have a chance to view and perhaps release
# such roles, set AUTOSKIP=false
# also, if you want to prompt to release role, set AUTOSKIP=false.
# otherwise, the script will go directly to the role release
AUTOSKIP="${AUTOSKIP:-true}"
# Find the last tag in each role
# Look at the merged PRs since that tag
# Automatically figure out what version to use for the new tag
# Automatically figure out what to put in the release notes for the release
# By default, if there are PR titles that do not follow the conventional
# commits format, the script will exit with an error. You can set
# ALLOW_BAD_PRS=true to continue to edit the CHANGELOG to deal with
# the bad PR titles.
ALLOW_BAD_PRS="${ALLOW_BAD_PRS:-false}"
# By default the script only collection PR titles. Set this to true to collect
# PR descriptions too.
USE_PR_BODY="${USE_PR_BODY:-false}"
# By default, AUTOSKIP=true will also skip roles that have only ci
# changes. If you want to release roles that have only ci changes,
# set AUTOSKIP_PR_TYPES=none
# If the role has only changes for PR types in this list, the role
# will be skipped. Otherwise, a new role release will be proposed.
AUTOSKIP_PR_TYPES="${AUTOSKIP_PR_TYPES:-ci}"
repo_entries="$(gh repo view --json name,owner)"
repo=${repo:-$(echo "$repo_entries" | jq -r .name)}
owner=${owner:-$(echo "$repo_entries" | jq -r .owner.login)}
# These roles are usually not released
NO_RELEASE_ROLES="${NO_RELEASE_ROLES:-template}"
for no_release_role in $NO_RELEASE_ROLES; do
if [ "$repo" = "$no_release_role" ]; then
echo not releasing role "$repo" - skipping
return 0
fi
done
get_main_branch() {
gh api "/repos/$owner/$repo" -q .default_branch
}
if ! type -p npm > /dev/null 2>&1; then
echo npm command not found
echo On Fedora, try 'sudo dnf install npm -y'
exit 1
fi
export npm_config_prefix="$HOME/.local"
export npm_config_global=True
if ! npm list @commitlint/cli > /dev/null 2>&1; then
npm install @commitlint/cli
fi
# There is a bug with config-conventional
# https://github.com/conventional-changelog/commitlint/issues/613
# workaround by setting NODE_PATH below
if ! npm list @commitlint/config-conventional > /dev/null 2>&1; then
npm install @commitlint/config-conventional
fi
export NODE_PATH="$npm_config_prefix/lib/node_modules/@commitlint/config-conventional/node_modules"
# get latest release
releases_latest=$(gh api repos/"$owner"/"$repo"/releases/latest -q '.tag_name,.published_at' 2> /dev/null || :)
skip=false
workdir="$(mktemp -d --suffix=_lsr)"
pushd "$workdir" > /dev/null 2>&1
# shellcheck disable=SC2064
trap "popd > /dev/null 2>&1; rm -rf $workdir" EXIT
pr_titles_file="$workdir/.pr_titles.txt"
prs_file="$workdir/.prs.json"
commitlint_errors_file="$workdir/.commitlint_errors.txt"
if [[ "$releases_latest" == *"Not Found"* ]]; then
# repo and LSR_GH_ORG are referenced but not assigned.
# shellcheck disable=SC2154
echo Repo for "${LSR_GH_ORG:-linux-system-roles}" "$repo" has no tags - create one below or skip
latest_tag=""
tag_range=""
else
latest_tag="$(echo "$releases_latest" | sed -n 1p)"
tag_range="closed:>$(echo "$releases_latest" | sed -n 2p)"
fi
cl_query="query { \
search(query: \"repo:$owner/$repo is:pr is:open docs(changelog) in:title\", type: ISSUE, first: 1) { \
issueCount \
edges { \
node { \
... on PullRequest { \
number \
title \
} \
} \
} \
} \
}"
cl_result="$(gh api graphql -f query="$cl_query" -q .data.search --jq '.data.search.edges[].node | "#\(.number) \(.title)"')"
# Note that first: 100 is a maximum, it would work only if there are <100 PRs since last tag
prs_query="$(cat <<EOF
query {
search(query: "repo:$owner/$repo is:pr is:merged $tag_range", type: ISSUE, first: 100) {
issueCount
edges {
node {
... on PullRequest {
number
title
body
mergedAt
}
}
}
}
}
EOF
)"
gh api graphql -f query="$prs_query" -q .data.search > "$prs_file"
count=$(jq '.issueCount' "$prs_file")
if [ -n "$cl_result" ]; then
echo There is already a PR for version changelog:
echo "$cl_result"
echo skipping tag/release for role "$repo"
echo ""
skip=true
elif [ "${count:-0}" = 0 ]; then
echo There are no merged PRs since latest tag "$latest_tag"
if [ "$AUTOSKIP" = true ]; then
echo Autoskip enabled - skipping tag/release for role "$repo"
echo ""
skip=true
else
echo ""
fi
else
echo Pull requests since latest tag "$latest_tag":
echo ""
: > "$pr_titles_file"
pr_descriptions=()
skip=true
# loop through pr_list in reverse to populate changelog in chronological order
for ((i="$count"-1; i>=0; i--)); do
pr_entry="$(jq '.edges['$i'].node' "$prs_file")"
pr_title="$(echo "$pr_entry" | jq -r '.title')"
pr_num="$(echo "$pr_entry" | jq -r '.number')"
pr_body="$(echo "$pr_entry" | jq -r '.body')"
echo "$pr_title" >> "$pr_titles_file"
# Only append PR body to PRs of type feat and fix
if
[ "${USE_PR_BODY}" = true ] &&
[ -n "$pr_body" ] &&
echo "$pr_title" | grep -e '^feat.*' -e '^fix.*'; then
# Indent pr_body 2 spaces to act as a bullet content for pr_title
pr_body_ind="$(echo "$pr_body" | awk '{ print " " $0 }')"
# Remove DOS carriage return ^M from PR body
# shellcheck disable=SC2001
# DOS carriage return ^M appears when PRs are edited using GH web UI
pr_body_rm_rt="$(echo "$pr_body_ind" | sed "s/$(printf '\r')\$//")"
# Remove 2 spaces on empty lines so that "cat --squeeze-blank" works
# shellcheck disable=SC2116
pr_body_rm_sp="$(echo "${pr_body_rm_rt// /}")"
printf -v pr_description -- "- %s (#%s)\n\n%s\n" "$pr_title" "$pr_num" "$pr_body_rm_sp"
else
printf -v pr_description -- "- %s (#%s)" "$pr_title" "$pr_num"
fi
pr_descriptions+=("$pr_description")
for pr_type in $AUTOSKIP_PR_TYPES; do
if [ "$pr_type" = none ]; then
skip=false # user does not want to skip
elif [[ "$pr_title" =~ ^"$pr_type": ]]; then
: # do nothing - do not touch skip value
else
skip=false # cannot skip - this is a "real" change
fi
done
done
cat "$pr_titles_file"
echo ""
# see the changes?
if [ "$AUTOSKIP" = false ]; then
read -r -p 'Release this role? (y/n)? (default: y) ' release_role
if [ "${release_role:-y}" = n ]; then
skip=true
fi
elif [ "$skip" = true ]; then
echo Autoskip enabled - the role has only PRs of type "$AUTOSKIP_PR_TYPES"
echo If you want to this role, set AUTOSKIP_PR_TYPES=none and re-run this command
echo ""
fi
fi
if [ "$skip" = false ]; then
if [ -s "$pr_titles_file" ] && [ "${ALLOW_BAD_PRS}" = false ] && [ "${count:-0}" != 0 ]; then
# get commitlint rc file
gh api /repos/"$owner"/"$repo"/contents/.commitlintrc.js -q .content | \
base64 --decode > "$workdir/.commitlintrc.js"
echo ""
echo Verifying if all PR titles comply with the conventional commits format ...
: > "$commitlint_errors_file"
while read -r pr_title; do
echo "$pr_title" | npx commitlint >> "$commitlint_errors_file" 2>&1 || :
done < "$pr_titles_file"
if [ -s "$commitlint_errors_file" ]; then
echo ERROR: the following PR titles failed commitlint the check:
cat "$commitlint_errors_file"
exit 1
else
echo Success
fi
fi
# special case for network and sshd
allow_v=""
case "$latest_tag" in
v*)
if [ "$repo" = ansible-sshd ] || [ "$repo" = sshd ]; then
# sshd uses a leading v
allow_v=v
else
# network had a case where there where two tags for the
# latest - one with leading v and one without - git describe
# would return the one with the leading v - so had to strip
# it off
latest_tag="${latest_tag//v}"
fi
;;
esac
if [[ "$latest_tag" =~ ^"$allow_v"([0-9]+)[.]([0-9]+)[.]([0-9]+)$ ]]; then
ver_major="${BASH_REMATCH[1]}"
ver_minor="${BASH_REMATCH[2]}"
ver_patch="${BASH_REMATCH[3]}"
elif [ -z "$latest_tag" ]; then
ver_major=0
ver_minor=0
ver_patch=0
else
echo ERROR: unexpected tag "$latest_tag"
exit 1
fi
if grep -q '^.*!:.*' "$pr_titles_file"; then
# Don't bump ver_major for prerelease versions (when ver_major=0)
if [[ "$ver_major" != 0 ]]; then
ver_major=$((ver_major+=1))
ver_minor=0
ver_patch=0
else
ver_major=0
ver_minor=$((ver_minor+=1))
ver_patch=0
fi
elif grep -q '^feat.*' "$pr_titles_file"; then
ver_minor=$((ver_minor+=1))
ver_patch=0
else
ver_patch=$((ver_patch+=1))
fi
new_tag="${allow_v}$ver_major.$ver_minor.$ver_patch"
while true; do
read -r -p "The script calculates the new tag based on PR titles types.
The previous tag is ${latest_tag:-EMPTY}.
The new tag is $new_tag.
Here are your options:
* To continue with the suggested new tag $new_tag and edit CHANGELOG.md, enter 'y',
* To provide a different tag, and edit CHANGELOG.md enter the new tag in the following format:
${allow_v}X.Y.Z, where X, Y, and Z are integers,
* To skip this role and go to the next role just press Enter. " new_tag_in
if [ -z "$new_tag_in" ]; then
break
elif [[ "$new_tag_in" =~ ^"$allow_v"[0-9]+[.][0-9]+[.][0-9]+$ ]]; then
new_tag=$new_tag_in
break
elif [ "$new_tag_in" == y ]; then
break
else
echo ""
echo ERROR: invalid input "$new_tag_in"
echo You must either input y or provide a new tag.
echo Tag must be in format "$allow_v"X.Y.Z
echo ""
fi
done
if [ -n "${new_tag_in}" ]; then
mainbr=$(get_main_branch)
rel_notes_file="$workdir/.release-notes-${new_tag}.md"
new_features_file="$workdir/.new_features.md"
bug_fixes_file="$workdir/.bug_fixes.md"
other_changes_file="$workdir/.other_changes.md"
tmp_changelog_file="$workdir/.tmp-changelog"
rm -f "$new_features_file" "$bug_fixes_file" "$other_changes_file"
for pr_description in "${pr_descriptions[@]}"; do
if echo "$pr_description" | sed -n 1p | grep '^- feat.*'; then
echo "$pr_description" >> "$new_features_file"
elif echo "$pr_description" | sed -n 1p | grep '^- fix.*'; then
echo "$pr_description" >> "$bug_fixes_file"
else
echo "$pr_description" >> "$other_changes_file"
fi
done
if [ ! -f "$rel_notes_file" ]; then
{ echo "[$new_tag] - $( date +%Y-%m-%d )"
echo "--------------------"
echo ""
} > "$rel_notes_file"
if [ -f "$new_features_file" ]; then
{ echo "### New Features"
echo ""
cat --squeeze-blank "$new_features_file"
echo ""
} >> "$rel_notes_file"
fi
if [ -f "$bug_fixes_file" ]; then
{ echo "### Bug Fixes"
echo ""
cat --squeeze-blank "$bug_fixes_file"
echo ""
} >> "$rel_notes_file"
fi
if [ -f "$other_changes_file" ]; then
{ echo "### Other Changes"
echo ""
cat --squeeze-blank "$other_changes_file"
echo ""
} >> "$rel_notes_file"
fi
fi
${EDITOR:-vi} "$rel_notes_file"
# this also will do a pushd to the checkedout repo dir
# shellcheck disable=SC2154
clone_repo . "$upstream_org" "$repo"
git checkout "$mainbr"
if [ -n "$(git branch --list "$BRANCH")" ]; then
git checkout "$BRANCH"
git rebase "$mainbr"
else
git checkout -b "$BRANCH"
fi
myheader="Changelog
========="
if [ -f CHANGELOG.md ]; then
clheader=$(head -2 CHANGELOG.md)
else
clheader="$myheader"
fi
if [ "$myheader" = "$clheader" ]; then
{ echo "$clheader"; echo ""; } > "$tmp_changelog_file"
cat "$rel_notes_file" >> "$tmp_changelog_file"
if [ -f CHANGELOG.md ]; then
tail -n +3 CHANGELOG.md >> "$tmp_changelog_file"
fi
else
echo WARNING: Changelog header "$clheader"
echo not in expected format "$myheader"
cat "$rel_notes_file" CHANGELOG.md > "$tmp_changelog_file"
fi
# Squeeze possible blank lines
cat --squeeze-blank "$tmp_changelog_file" > CHANGELOG.md
gh api /repos/"$owner"/"$repo"/contents/latest/README.html?ref=docs -q .content | base64 --decode > .README.html
git add CHANGELOG.md .README.html
{ echo "docs(changelog): version $new_tag [citest skip]"; echo "";
echo "Update changelog and .README.html for version $new_tag"; } > .gitcommitmsg
git commit -s -F .gitcommitmsg
rm -f .gitcommitmsg "$rel_notes_file" "$new_features_file" \
"$bug_fixes_file" "$other_changes_file" "$pr_titles_file" \
"$commitlint_errors_file" "$prs_file" "$tmp_changelog_file"
if [ -n "${origin_org:-}" ]; then
git push -u origin "$BRANCH"
gh pr create --fill --base "$mainbr" --head "$origin_org":"$BRANCH"
fi
popd > /dev/null 2>&1 # clone_repo does a pushd to repo dir
fi
echo ""
echo ""
fi
trap - EXIT
popd > /dev/null 2>&1
if [[ "$workdir" =~ ^/tmp ]]; then
rm -rf "$workdir"
fi