Skip to content

Commit 5c96987

Browse files
[FEATURE] Improved changelog tool even more (- WIP #155 -)
Changes in file generate_changelog.sh: * improved comments * imporved details table * added optional verbose mode * added link to full changelog view on github
1 parent 94e82bd commit 5c96987

File tree

1 file changed

+222
-21
lines changed

1 file changed

+222
-21
lines changed

generate_changelog.sh

Lines changed: 222 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,187 @@
11
#! /bin/bash
2+
# Disclaimer of Warranties.
3+
# A. YOU EXPRESSLY ACKNOWLEDGE AND AGREE THAT, TO THE EXTENT PERMITTED BY
4+
# APPLICABLE LAW, USE OF THIS SHELL SCRIPT AND ANY SERVICES PERFORMED
5+
# BY OR ACCESSED THROUGH THIS SHELL SCRIPT IS AT YOUR SOLE RISK AND
6+
# THAT THE ENTIRE RISK AS TO SATISFACTORY QUALITY, PERFORMANCE, ACCURACY AND
7+
# EFFORT IS WITH YOU.
8+
#
9+
# B. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SHELL SCRIPT
10+
# AND SERVICES ARE PROVIDED "AS IS" AND “AS AVAILABLE”, WITH ALL FAULTS AND
11+
# WITHOUT WARRANTY OF ANY KIND, AND THE AUTHOR OF THIS SHELL SCRIPT'S LICENSORS
12+
# (COLLECTIVELY REFERRED TO AS "THE AUTHOR" FOR THE PURPOSES OF THIS DISCLAIMER)
13+
# HEREBY DISCLAIM ALL WARRANTIES AND CONDITIONS WITH RESPECT TO THIS SHELL SCRIPT
14+
# SOFTWARE AND SERVICES, EITHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT
15+
# NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF
16+
# MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A PARTICULAR PURPOSE,
17+
# ACCURACY, QUIET ENJOYMENT, AND NON-INFRINGEMENT OF THIRD PARTY RIGHTS.
18+
#
19+
# C. THE AUTHOR DOES NOT WARRANT AGAINST INTERFERENCE WITH YOUR ENJOYMENT OF THE
20+
# THE AUTHOR's SOFTWARE AND SERVICES, THAT THE FUNCTIONS CONTAINED IN, OR
21+
# SERVICES PERFORMED OR PROVIDED BY, THIS SHELL SCRIPT WILL MEET YOUR
22+
# REQUIREMENTS, THAT THE OPERATION OF THIS SHELL SCRIPT OR SERVICES WILL
23+
# BE UNINTERRUPTED OR ERROR-FREE, THAT ANY SERVICES WILL CONTINUE TO BE MADE
24+
# AVAILABLE, THAT THIS SHELL SCRIPT OR SERVICES WILL BE COMPATIBLE OR
25+
# WORK WITH ANY THIRD PARTY SOFTWARE, APPLICATIONS OR THIRD PARTY SERVICES,
26+
# OR THAT DEFECTS IN THIS SHELL SCRIPT OR SERVICES WILL BE CORRECTED.
27+
# INSTALLATION OF THIS THE AUTHOR SOFTWARE MAY AFFECT THE USABILITY OF THIRD
28+
# PARTY SOFTWARE, APPLICATIONS OR THIRD PARTY SERVICES.
29+
#
30+
# D. YOU FURTHER ACKNOWLEDGE THAT THIS SHELL SCRIPT AND SERVICES ARE NOT
31+
# INTENDED OR SUITABLE FOR USE IN SITUATIONS OR ENVIRONMENTS WHERE THE FAILURE
32+
# OR TIME DELAYS OF, OR ERRORS OR INACCURACIES IN, THE CONTENT, DATA OR
33+
# INFORMATION PROVIDED BY THIS SHELL SCRIPT OR SERVICES COULD LEAD TO
34+
# DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE,
35+
# INCLUDING WITHOUT LIMITATION THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
36+
# NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, LIFE SUPPORT OR
37+
# WEAPONS SYSTEMS.
38+
#
39+
# E. NO ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY THE AUTHOR
40+
# SHALL CREATE A WARRANTY. SHOULD THIS SHELL SCRIPT OR SERVICES PROVE DEFECTIVE,
41+
# YOU ASSUME THE ENTIRE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
42+
#
43+
# Limitation of Liability.
44+
# F. TO THE EXTENT NOT PROHIBITED BY APPLICABLE LAW, IN NO EVENT SHALL THE AUTHOR
45+
# BE LIABLE FOR PERSONAL INJURY, OR ANY INCIDENTAL, SPECIAL, INDIRECT OR
46+
# CONSEQUENTIAL DAMAGES WHATSOEVER, INCLUDING, WITHOUT LIMITATION, DAMAGES
47+
# FOR LOSS OF PROFITS, CORRUPTION OR LOSS OF DATA, FAILURE TO TRANSMIT OR
48+
# RECEIVE ANY DATA OR INFORMATION, BUSINESS INTERRUPTION OR ANY OTHER
49+
# COMMERCIAL DAMAGES OR LOSSES, ARISING OUT OF OR RELATED TO YOUR USE OR
50+
# INABILITY TO USE THIS SHELL SCRIPT OR SERVICES OR ANY THIRD PARTY
51+
# SOFTWARE OR APPLICATIONS IN CONJUNCTION WITH THIS SHELL SCRIPT OR
52+
# SERVICES, HOWEVER CAUSED, REGARDLESS OF THE THEORY OF LIABILITY (CONTRACT,
53+
# TORT OR OTHERWISE) AND EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
54+
# POSSIBILITY OF SUCH DAMAGES. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
55+
# OR LIMITATION OF LIABILITY FOR PERSONAL INJURY, OR OF INCIDENTAL OR
56+
# CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY TO YOU. In no event
57+
# shall THE AUTHOR's total liability to you for all damages (other than as may
58+
# be required by applicable law in cases involving personal injury) exceed
59+
# the amount of five dollars ($5.00). The foregoing limitations will apply
60+
# even if the above stated remedy fails of its essential purpose.
61+
################################################################################
262

3-
# Declare a varaiable for caching
4-
declare cache;
63+
ulimit -t 1200
64+
PATH="/bin:/sbin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:${PATH}"
65+
# shellcheck disable=SC2086
66+
LANG=${LANG:-"en_US"}
67+
LC_ALL="${LANG:0:5}.utf-8"
68+
umask 137
569

70+
# Function to check if a command exists.
71+
72+
# USAGE:
73+
# ~$ check_command CMD
74+
# Arguments:
75+
# CMD (Required) -- Name of the command to check
76+
# Results:
77+
# exits 64 -- missing required argument
78+
# exits 126 -- check complete and has failed, can not find given command.
79+
# returns successful -- check complete and command found to be executable.
80+
function check_command() {
81+
test -z "$1" && { printf "%s\n" "Error: command name is required to check for existence." >&2 ; exit 64 ; } ;
82+
local cmd="$1" ;
83+
# shellcheck disable=SC2086
84+
test -x "$(command -v ${cmd})" || { printf "%s\n" "Error: Required command '$cmd' is not found." >&2 ; exit 126 ; } ;
85+
} # end check_command()
86+
# propagate/export function to sub-shells
87+
export -f check_command
88+
89+
# Check required commands
90+
check_command awk ;
91+
check_command cat ;
92+
check_command cut ;
93+
check_command git ;
94+
check_command grep ;
95+
check_command head ;
96+
check_command sed ;
97+
check_command sort ;
98+
check_command tail ;
99+
check_command tee ;
100+
check_command tr ;
101+
check_command uniq ;
102+
check_command xargs ;
103+
104+
# rest of the script vars
105+
LOG_FILE="chglog_generation_${PPID}.log"
106+
ERR_FILE="chglog_generation_errors_${PPID}.log"
107+
LOCK_FILE="${TMPDIR:-/tmp}/org.pak.multicast.chglog-generation-shell"
108+
EXIT_CODE=0
109+
110+
# USAGE:
111+
# ~$ id_current_tag INPUT_HINT_FOR_CURRENT
112+
# Arguments:
113+
# INPUT (Required) -- Hint reference for the current tag to retrieve (e.g., HEAD or current SHA)
114+
# Results:
115+
# returns the most recent tag associated with the current commit.
6116
function id_current_tag() {
7-
local INPUT="${1}"
8-
git describe --tags --abbrev=0 ${INPUT}
9-
}
117+
local INPUT_HINT_FOR_CURRENT="${1}"
118+
git describe --tags --abbrev=0 ${INPUT_HINT_FOR_CURRENT}
119+
} # end id_current_tag()
10120

121+
# USAGE:
122+
# ~$ run_enumerate_tag_history
123+
# Results:
124+
# returns a list of tags that are ancestors of the current HEAD, excluding the current tag.
11125
function run_enumerate_tag_history() {
12126
local HEAD_TAG=$(id_current_tag HEAD)
13127
{ git rev-list --tags --ancestry-path "$(git tag --no-contains "${HEAD_TAG}" | sort -V | head -n1)^..HEAD" ; wait ;} | xargs -I{} git tag --points-at "{}" 2>/dev/null ;
14128
wait ;
15-
}
129+
} # end run_enumerate_tag_history()
16130

131+
# USAGE:
132+
# ~$ enumerate_tag_history
133+
# Results:
134+
# returns a cached list of tags that are ancestors of the current HEAD, excluding development tags.
135+
# If the cache is empty, it populates the cache by calling run_enumerate_tag_history.
17136
function enumerate_tag_history() {
18137
if [[ -z "${cache}" ]] ; then
19138
cache=$(run_enumerate_tag_history | grep -vE "v?\d+.\d+.\d+-dev" ; wait;)
20139
fi ;
21140
printf "%s\n" "${cache}" ;
22141
wait ;
23-
}
142+
} # end enumerate_tag_history()
24143

144+
# USAGE:
145+
# ~$ id_parent_tag INPUT
146+
# Arguments:
147+
# INPUT (Required) -- The tag or commit to find the parent tag for
148+
# Results:
149+
# returns the parent tag of the specified input tag or commit.
25150
function id_parent_tag() {
26151
local INPUT="${1}"
27152
enumerate_tag_history | grep -A 1 -F -f <(id_current_tag ${INPUT} ) | tail -n1
28153
wait ;
29-
}
154+
} # end id_parent_tag()
30155

31156
# step 1: is designed to determine the current and previous Git tags and
32157
# then construct a Git range based on these tags. If no argument is provided, it defaults to
33158
# using the tags.
34159
if [[ -z "${1}" ]] ; then
35-
# GIT_CURRENT_TAG=$(id_current_tag)
36-
37-
for EACH_TAG in $(enumerate_tag_history | sort -Vr) ; do
38-
GIT_PREVIOUS_TAG=$(id_parent_tag "${EACH_TAG}")
39-
FALLBACK_GIT_RANGE="${EACH_TAG}...${GIT_PREVIOUS_TAG}"
40-
"${0}" "${FALLBACK_GIT_RANGE}" || : ; wait ;
41-
done;
42-
exit 0 ;
160+
# GIT_CURRENT_TAG=$(id_current_tag)
161+
printf "# Changelog\n" ;
162+
for EACH_TAG in $(enumerate_tag_history | sort -Vr) ; do
163+
GIT_PREVIOUS_TAG=$(id_parent_tag "${EACH_TAG}")
164+
if [[ ( -n "${GIT_PREVIOUS_TAG}" ) ]] && [[ ( "${EACH_TAG}" != "${GIT_PREVIOUS_TAG}" ) ]] ; then
165+
FALLBACK_GIT_RANGE="${EACH_TAG}...${GIT_PREVIOUS_TAG}"
166+
"${0}" "${FALLBACK_GIT_RANGE}" || : ; wait ;
167+
fi ;
168+
done;
169+
unset FALLBACK_GIT_RANGE 2>/dev/null || : ;
170+
unset GIT_PREVIOUS_TAG 2>/dev/null || : ;
171+
exit 0 ;
43172
fi ;
44-
GIT_RANGE="${1:-${FALLBACK_GIT_RANGE}}"
173+
GIT_RANGE="${1}"
45174

46-
# cache the git log
47-
CHANGELOG_BUFFER=".chagelog_buffer.txt"
175+
# cache the git full log
176+
CHANGELOG_BUFFER="${TMPDIR:-/tmp}/.chagelog_buffer.txt"
48177
cat <(git log "${GIT_RANGE}" --reverse --pretty=format:"COMMIT_START%n%h%n%B%nCOMMIT_END") >"${CHANGELOG_BUFFER}" ; wait ;
49178

179+
RAW_FLAGS_LIST=$(cat <"${CHANGELOG_BUFFER}" | grep -oE "([\[][A-Z]+[]]){1}" | sort -id | uniq -c | sort -rid | grep -oE "([A-Z]+){1}" | sort -id | uniq | sort -rd ; wait ;)
180+
50181
RAW_IMPACTED_ISSUES=$(cat <"${CHANGELOG_BUFFER}" | grep -oE "([#]\d+){1}\b" | sort -id | uniq | xargs -L1 -I{} printf "%s, " "{}" ; wait ;)
51-
RAW_FLAGS_USED=$(cat <"${CHANGELOG_BUFFER}" | grep -oE "^([\[][A-Z]+[]]){1}" | sort -id | uniq -c | sort -rid | grep -oE "([A-Z]+){1}" | xargs -L1 -I{} printf "%s, " "{}" ; wait ;)
182+
RAW_FLAGS_USED=$(cat <(printf "%s\n" "${RAW_FLAGS_LIST}") | xargs -L1 -I{} printf "%s, " "{}" ; wait ;)
183+
RAW_NEW_FILES=$(cat <"${CHANGELOG_BUFFER}" | grep -F "Additions with file" | sort -id | cut -d\ -f4- | sort -id | uniq -c | tr -s ' ' ' ' | cut -d\ -f 3- | cut -d: -f 1-1 | xargs -L1 -I{} printf "%s, " "{}" ; wait ;)
184+
RAW_DEL_FILES=$(cat <"${CHANGELOG_BUFFER}" | grep -F "Deletions from file" | sort -id | cut -d\ -f4- | sort -id | uniq -c | tr -s ' ' ' ' | cut -d\ -f 3- | cut -d: -f 1-1 | xargs -L1 -I{} printf "%s, " "{}" ; wait ;)
52185

53186

54187
# header
@@ -59,7 +192,65 @@ printf "%s" "| Kinds of changes | " ;
59192
printf "%s\n" "${RAW_FLAGS_USED:-'None'} |" | sed -e 's/, |/ |/g' ;
60193
printf "%s" "| Impacted Issues | " ;
61194
printf "%s\n" "${RAW_IMPACTED_ISSUES:-'None'} |" | sed -e 's/, |/ |/g' ;
195+
if [[ ( -n "${RAW_NEW_FILES}" ) ]] ; then
196+
printf "%s" "| New Files | " ;
197+
printf "%s\n" "${RAW_NEW_FILES:-'None'} |" | sed -e 's/, |/ |/g' ;
198+
fi ;
199+
if [[ ( -n "${RAW_DEL_FILES}" ) ]] ; then
200+
printf "%s" "| Removed Files | " ;
201+
printf "%s\n" "${RAW_DEL_FILES:-'None'} |" | sed -e 's/, |/ |/g' ;
202+
fi ;
203+
204+
if [[ ( ${VERBOSE_CHANGE_LOG:-0} -gt 0 ) ]] ; then
205+
# flags sub-header
206+
printf "\n### Changes by Kind\n" ;
207+
208+
# cache the git log summaries with hashes
209+
CHANGELOG_BUFFER_SHORT="${TMPDIR:-/tmp}/.short chagelog_buffer.txt"
210+
cat <(git log "${GIT_RANGE}" --reverse --grep="([\[][A-Z]+[]]){1}" -E --pretty=format:"COMMIT_START%n%h %s%nCOMMIT_END") >"${CHANGELOG_BUFFER_SHORT}" ; wait ;
211+
212+
# auto-collect by flags
213+
for FLAG_ID in $(printf "%s\n" "${RAW_FLAGS_LIST}") ; do
214+
215+
if [[ ( -n "$FLAG_ID" ) ]] ; then
216+
217+
awk -v RS='\n' -v ORS='\n\n\n' -v flagname="$FLAG_ID" '
218+
{
219+
# Check if the block contains a valid change-log entry
220+
if ($0 ~ /([\[][A-Z]+[]]){1}/ && $0 ~ flagname) {
221+
# Ensure that there is no content before the match
222+
if ($0 ~ /^[a-f0-9]{7,7}[[:space:]]*(([\[][A-Z]+[]]){1})/) {
223+
# Extract the header (first line) and the content
224+
header = $0;
225+
hash_line = substr(header, 1, 7); # Get the hash
226+
header_line = substr(header, index(header, "[") + 1, (index(header, "]") - index(header, "[")) - 1); # Get the header
227+
if (header_line ~ flagname) {
228+
content = " * " hash_line " --" substr(header, index(header, "]") + 1); # Get the content after the header
229+
# Combine the header and content
230+
combined[header_line] = (combined[header_line] ? combined[header_line] "\n" : "") content;
231+
}
232+
}
233+
}
234+
}
235+
END {
236+
# Print combined entries
237+
for (h in combined) {
238+
print h ":\n" combined[h]
239+
}
240+
}
241+
' <"${CHANGELOG_BUFFER_SHORT}" | sort -id | uniq | sort -id -k 4
242+
243+
fi ;
244+
245+
done ;
246+
247+
rm -f "${CHANGELOG_BUFFER_SHORT}" 2>/dev/null || : ;
248+
# files sub-header
249+
printf "\n### Changes by file\n" ;
250+
else
251+
# NO sub-headers
62252
printf "\n\n" ;
253+
fi ;
63254

64255
# auto-collect gitlog
65256
for FILE_INPUT in $(git log --pretty=format:"%n" --name-only "${GIT_RANGE}" | sort -id | uniq) ; do
@@ -73,7 +264,7 @@ awk -v RS='' -v ORS='\n\n\n' -v filename="$FILE_INPUT" '
73264
# Ensure that there is no content before the match
74265
if ($0 ~ /^[[:space:]]*((Changes in)|(Additions with)|(Deletions from))/) {
75266
# Extract the header (first line) and the content
76-
header = $0;
267+
header = $0;
77268
header_line = substr(header, 1, index(header, ":") - 1); # Get the header
78269
if (header_line ~ filename) {
79270
content = substr(header, index(header, ":") + 1); # Get the content after the header
@@ -94,8 +285,18 @@ awk -v RS='' -v ORS='\n\n\n' -v filename="$FILE_INPUT" '
94285
fi ;
95286

96287
done ;
288+
# diff link
289+
printf "**Full Changelog**: %s(%s)\n\n***\n\n" "[\`${GIT_RANGE}\`]" \
290+
"https://github.com/reactive-firewall/multicast/compare/${GIT_RANGE}" ;
97291

292+
# cleanup
293+
unset RAW_NEW_FILES 2>/dev/null || : ;
294+
unset RAW_IMPACTED_ISSUES 2>/dev/null || : ;
295+
unset RAW_FLAGS_USED 2>/dev/null || : ;
296+
unset GIT_RANGE 2>/dev/null || : ;
98297
unset cache 2>/dev/null || : ;
99298

100299
# remove the buffer
101300
rm -f "${CHANGELOG_BUFFER}" 2>/dev/null || : ;
301+
302+
exit ${EXIT_CODE:-255} ;

0 commit comments

Comments
 (0)