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.
6116function 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.
11125function 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.
17136function 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.
25150function 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.
34159if [[ -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 ;
43172fi ;
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"
48177cat <( 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+
50181RAW_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 | " ;
59192printf " %s\n" " ${RAW_FLAGS_USED:- ' None' } |" | sed -e ' s/, |/ |/g' ;
60193printf " %s" " | Impacted Issues | " ;
61194printf " %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
62252printf " \n\n" ;
253+ fi ;
63254
64255# auto-collect gitlog
65256for 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" '
94285fi ;
95286
96287done ;
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 || : ;
98297unset cache 2> /dev/null || : ;
99298
100299# remove the buffer
101300rm -f " ${CHANGELOG_BUFFER} " 2> /dev/null || : ;
301+
302+ exit ${EXIT_CODE:- 255} ;
0 commit comments