| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  | #!/usr/bin/env zsh
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-26 18:24:29 +02:00
										 |  |  | cd "$ZSH" | 
					
						
							|  |  |  | setopt extendedglob | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  | ############################## | 
					
						
							|  |  |  | # CHANGELOG SCRIPT CONSTANTS # | 
					
						
							|  |  |  | ############################## | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #* Holds the list of valid types recognized in a commit subject | 
					
						
							|  |  |  | #* and the display string of such type | 
					
						
							|  |  |  | local -A TYPES | 
					
						
							|  |  |  | TYPES=( | 
					
						
							| 
									
										
										
										
											2020-12-07 19:53:11 +01:00
										 |  |  |   build     "Build system" | 
					
						
							|  |  |  |   chore     "Chore" | 
					
						
							|  |  |  |   ci        "CI" | 
					
						
							|  |  |  |   docs      "Documentation" | 
					
						
							|  |  |  |   feat      "Features" | 
					
						
							|  |  |  |   fix       "Bug fixes" | 
					
						
							|  |  |  |   perf      "Performance" | 
					
						
							|  |  |  |   refactor  "Refactor" | 
					
						
							|  |  |  |   style     "Style" | 
					
						
							|  |  |  |   test      "Testing" | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-28 13:22:31 +01:00
										 |  |  | #* Types that will be displayed in their own section, in the order specified here. | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  | local -a MAIN_TYPES | 
					
						
							|  |  |  | MAIN_TYPES=(feat fix perf docs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #* Types that will be displayed under the category of other changes | 
					
						
							|  |  |  | local -a OTHER_TYPES | 
					
						
							|  |  |  | OTHER_TYPES=(refactor style other) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #* Commit types that don't appear in $MAIN_TYPES nor $OTHER_TYPES | 
					
						
							|  |  |  | #* will not be displayed and will simply be ignored. | 
					
						
							| 
									
										
										
										
											2022-01-28 13:22:31 +01:00
										 |  |  | local -a IGNORED_TYPES | 
					
						
							|  |  |  | IGNORED_TYPES=(${${${(@k)TYPES}:|MAIN_TYPES}:|OTHER_TYPES}) | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | ############################ | 
					
						
							|  |  |  | # COMMIT PARSING UTILITIES # | 
					
						
							|  |  |  | ############################ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function parse-commit { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # This function uses the following globals as output: commits (A), | 
					
						
							|  |  |  |   # subjects (A), scopes (A) and breaking (A). All associative arrays (A) | 
					
						
							|  |  |  |   # have $hash as the key. | 
					
						
							|  |  |  |   # - commits holds the commit type | 
					
						
							|  |  |  |   # - subjects holds the commit subject | 
					
						
							|  |  |  |   # - scopes holds the scope of a commit | 
					
						
							|  |  |  |   # - breaking holds the breaking change warning if a commit does | 
					
						
							|  |  |  |   #   make a breaking change | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function commit:type { | 
					
						
							| 
									
										
										
										
											2021-10-26 18:59:39 +02:00
										 |  |  |     local type | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Parse commit type from the subject | 
					
						
							|  |  |  |     if [[ "$1" =~ '^([a-zA-Z_\-]+)(\(.+\))?!?: .+$' ]]; then | 
					
						
							|  |  |  |       type="${match[1]}" | 
					
						
							|  |  |  |     fi | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # If $type doesn't appear in $TYPES array mark it as 'other' | 
					
						
							| 
									
										
										
										
											2021-10-26 18:59:39 +02:00
										 |  |  |     if [[ -n "$type" && -n "${(k)TYPES[(i)$type]}" ]]; then | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |       echo $type | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       echo other | 
					
						
							|  |  |  |     fi | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function commit:scope { | 
					
						
							|  |  |  |     local scope | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Try to find scope in "type(<scope>):" format | 
					
						
							| 
									
										
										
										
											2021-10-26 18:59:39 +02:00
										 |  |  |     if [[ "$1" =~ '^[a-zA-Z_\-]+\((.+)\)!?: .+$' ]]; then | 
					
						
							|  |  |  |       echo "${match[1]}" | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |       return | 
					
						
							|  |  |  |     fi | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # If no scope found, try to find it in "<scope>:" format | 
					
						
							| 
									
										
										
										
											2021-10-26 18:59:39 +02:00
										 |  |  |     if [[ "$1" =~ '^([a-zA-Z_\-]+): .+$' ]]; then | 
					
						
							|  |  |  |       scope="${match[1]}" | 
					
						
							|  |  |  |       # Make sure it's not a type before printing it | 
					
						
							|  |  |  |       if [[ -z "${(k)TYPES[(i)$scope]}" ]]; then | 
					
						
							|  |  |  |         echo "$scope" | 
					
						
							|  |  |  |       fi | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     fi | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function commit:subject { | 
					
						
							|  |  |  |     # Only display the relevant part of the commit, i.e. if it has the format | 
					
						
							|  |  |  |     # type[(scope)!]: subject, where the part between [] is optional, only | 
					
						
							|  |  |  |     # displays subject. If it doesn't match the format, returns the whole string. | 
					
						
							| 
									
										
										
										
											2021-10-26 18:59:39 +02:00
										 |  |  |     if [[ "$1" =~ '^[a-zA-Z_\-]+(\(.+\))?!?: (.+)$' ]]; then | 
					
						
							|  |  |  |       echo "${match[2]}" | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       echo "$1" | 
					
						
							|  |  |  |     fi | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Return subject if the body or subject match the breaking change format | 
					
						
							|  |  |  |   function commit:is-breaking { | 
					
						
							| 
									
										
										
										
											2020-12-12 14:22:26 +01:00
										 |  |  |     local subject="$1" body="$2" message | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if [[ "$body" =~ "BREAKING CHANGE: (.*)" || \
 | 
					
						
							|  |  |  |       "$subject" =~ '^[^ :\)]+\)?!: (.*)$' ]]; then | 
					
						
							| 
									
										
										
										
											2020-12-12 14:22:26 +01:00
										 |  |  |       message="${match[1]}" | 
					
						
							| 
									
										
										
										
											2020-12-30 20:02:24 +01:00
										 |  |  |       # remove CR characters (might be inserted in GitHub UI commit description form) | 
					
						
							|  |  |  |       message="${message//$'\r'/}" | 
					
						
							| 
									
										
										
										
											2020-12-12 14:22:26 +01:00
										 |  |  |       # skip next paragraphs (separated by two newlines or more) | 
					
						
							|  |  |  |       message="${message%%$'\n\n'*}" | 
					
						
							|  |  |  |       # ... and replace newlines with spaces | 
					
						
							|  |  |  |       echo "${message//$'\n'/ }" | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     else | 
					
						
							|  |  |  |       return 1 | 
					
						
							|  |  |  |     fi | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Return truncated hash of the reverted commit | 
					
						
							|  |  |  |   function commit:is-revert { | 
					
						
							|  |  |  |     local subject="$1" body="$2" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if [[ "$subject" = Revert* && \
 | 
					
						
							|  |  |  |       "$body" =~ "This reverts commit ([^.]+)\." ]]; then | 
					
						
							|  |  |  |       echo "${match[1]:0:7}" | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       return 1 | 
					
						
							|  |  |  |     fi | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Parse commit with hash $1 | 
					
						
							| 
									
										
										
										
											2021-10-26 18:24:29 +02:00
										 |  |  |   local hash="$1" subject="$2" body="$3" warning rhash | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   # Commits following Conventional Commits (https://www.conventionalcommits.org/) | 
					
						
							|  |  |  |   # have the following format, where parts between [] are optional: | 
					
						
							|  |  |  |   # | 
					
						
							|  |  |  |   #  type[(scope)][!]: subject | 
					
						
							|  |  |  |   # | 
					
						
							|  |  |  |   #  commit body | 
					
						
							|  |  |  |   #  [BREAKING CHANGE: warning] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # commits holds the commit type | 
					
						
							| 
									
										
										
										
											2022-01-28 13:22:31 +01:00
										 |  |  |   types[$hash]="$(commit:type "$subject")" | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |   # scopes holds the commit scope | 
					
						
							|  |  |  |   scopes[$hash]="$(commit:scope "$subject")" | 
					
						
							|  |  |  |   # subjects holds the commit subject | 
					
						
							|  |  |  |   subjects[$hash]="$(commit:subject "$subject")" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # breaking holds whether a commit has breaking changes | 
					
						
							|  |  |  |   # and its warning message if it does | 
					
						
							|  |  |  |   if warning=$(commit:is-breaking "$subject" "$body"); then | 
					
						
							|  |  |  |     breaking[$hash]="$warning" | 
					
						
							|  |  |  |   fi | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # reverts holds commits reverted in the same release | 
					
						
							|  |  |  |   if rhash=$(commit:is-revert "$subject" "$body"); then | 
					
						
							|  |  |  |     reverts[$hash]=$rhash | 
					
						
							|  |  |  |   fi | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-31 18:49:59 +13:00
										 |  |  | ################################ | 
					
						
							|  |  |  | # SUPPORTS HYPERLINKS FUNCTION # | 
					
						
							|  |  |  | ################################ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # The code for checking if a terminal supports hyperlinks is copied from install.sh | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # The [ -t 1 ] check only works when the function is not called from | 
					
						
							|  |  |  | # a subshell (like in `$(...)` or `(...)`, so this hack redefines the | 
					
						
							|  |  |  | # function at the top level to always return false when stdout is not | 
					
						
							|  |  |  | # a tty. | 
					
						
							|  |  |  | if [ -t 1 ]; then | 
					
						
							|  |  |  |   is_tty() { | 
					
						
							|  |  |  |     true | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | else | 
					
						
							|  |  |  |   is_tty() { | 
					
						
							|  |  |  |     false | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | fi | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # This function uses the logic from supports-hyperlinks[1][2], which is | 
					
						
							|  |  |  | # made by Kat Marchán (@zkat) and licensed under the Apache License 2.0. | 
					
						
							|  |  |  | # [1] https://github.com/zkat/supports-hyperlinks | 
					
						
							|  |  |  | # [2] https://crates.io/crates/supports-hyperlinks | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Copyright (c) 2021 Kat Marchán | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  | # you may not use this file except in compliance with the License. | 
					
						
							|  |  |  | # You may obtain a copy of the License at | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | #     http://www.apache.org/licenses/LICENSE-2.0 | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  | # distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  | # See the License for the specific language governing permissions and | 
					
						
							|  |  |  | # limitations under the License. | 
					
						
							|  |  |  | supports_hyperlinks() { | 
					
						
							|  |  |  |   # $FORCE_HYPERLINK must be set and be non-zero (this acts as a logic bypass) | 
					
						
							|  |  |  |   if [ -n "$FORCE_HYPERLINK" ]; then | 
					
						
							|  |  |  |     [ "$FORCE_HYPERLINK" != 0 ] | 
					
						
							|  |  |  |     return $? | 
					
						
							|  |  |  |   fi | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # If stdout is not a tty, it doesn't support hyperlinks | 
					
						
							|  |  |  |   is_tty || return 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # DomTerm terminal emulator (domterm.org) | 
					
						
							|  |  |  |   if [ -n "$DOMTERM" ]; then | 
					
						
							|  |  |  |     return 0 | 
					
						
							|  |  |  |   fi | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # VTE-based terminals above v0.50 (Gnome Terminal, Guake, ROXTerm, etc) | 
					
						
							|  |  |  |   if [ -n "$VTE_VERSION" ]; then | 
					
						
							|  |  |  |     [ $VTE_VERSION -ge 5000 ] | 
					
						
							|  |  |  |     return $? | 
					
						
							|  |  |  |   fi | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # If $TERM_PROGRAM is set, these terminals support hyperlinks | 
					
						
							|  |  |  |   case "$TERM_PROGRAM" in | 
					
						
							|  |  |  |   Hyper|iTerm.app|terminology|WezTerm) return 0 ;; | 
					
						
							|  |  |  |   esac | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # kitty supports hyperlinks | 
					
						
							|  |  |  |   if [ "$TERM" = xterm-kitty ]; then | 
					
						
							|  |  |  |     return 0 | 
					
						
							|  |  |  |   fi | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Windows Terminal also supports hyperlinks | 
					
						
							|  |  |  |   if [ -n "$WT_SESSION" ]; then | 
					
						
							|  |  |  |     return 0 | 
					
						
							|  |  |  |   fi | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Konsole supports hyperlinks, but it's an opt-in setting that can't be detected | 
					
						
							|  |  |  |   # https://github.com/ohmyzsh/ohmyzsh/issues/10964 | 
					
						
							|  |  |  |   # if [ -n "$KONSOLE_VERSION" ]; then | 
					
						
							|  |  |  |   #   return 0 | 
					
						
							|  |  |  |   # fi | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return 1 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  | ############################# | 
					
						
							|  |  |  | # RELEASE CHANGELOG DISPLAY # | 
					
						
							|  |  |  | ############################# | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function display-release { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # This function uses the following globals: output, version, | 
					
						
							| 
									
										
										
										
											2022-01-28 13:22:31 +01:00
										 |  |  |   # types (A), subjects (A), scopes (A), breaking (A) and reverts (A). | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |   # | 
					
						
							|  |  |  |   # - output is the output format to use when formatting (raw|text|md) | 
					
						
							|  |  |  |   # - version is the version in which the commits are made | 
					
						
							| 
									
										
										
										
											2022-01-28 13:22:31 +01:00
										 |  |  |   # - types, subjects, scopes, breaking, and reverts are associative arrays | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |   #   with commit hashes as keys | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Remove commits that were reverted | 
					
						
							|  |  |  |   local hash rhash | 
					
						
							|  |  |  |   for hash rhash in ${(kv)reverts}; do | 
					
						
							| 
									
										
										
										
											2022-01-28 13:22:31 +01:00
										 |  |  |     if (( ${+types[$rhash]} )); then | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |       # Remove revert commit | 
					
						
							| 
									
										
										
										
											2022-01-28 13:22:31 +01:00
										 |  |  |       unset "types[$hash]" "subjects[$hash]" "scopes[$hash]" "breaking[$hash]" | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |       # Remove reverted commit | 
					
						
							| 
									
										
										
										
											2022-01-28 13:22:31 +01:00
										 |  |  |       unset "types[$rhash]" "subjects[$rhash]" "scopes[$rhash]" "breaking[$rhash]" | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     fi | 
					
						
							|  |  |  |   done | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-28 13:22:31 +01:00
										 |  |  |   # Remove commits from ignored types unless it has breaking change information | 
					
						
							|  |  |  |   for hash in ${(k)types[(R)${(j:|:)IGNORED_TYPES}]}; do | 
					
						
							|  |  |  |     (( ! ${+breaking[$hash]} )) || continue | 
					
						
							|  |  |  |     unset "types[$hash]" "subjects[$hash]" "scopes[$hash]" | 
					
						
							|  |  |  |   done | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |   # If no commits left skip displaying the release | 
					
						
							| 
									
										
										
										
											2022-01-28 13:22:31 +01:00
										 |  |  |   if (( $#types == 0 )); then | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     return | 
					
						
							|  |  |  |   fi | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-07 17:08:46 +02:00
										 |  |  |   # Get length of longest scope for padding | 
					
						
							|  |  |  |   local max_scope=0 | 
					
						
							|  |  |  |   for hash in ${(k)scopes}; do | 
					
						
							|  |  |  |     max_scope=$(( max_scope < ${#scopes[$hash]} ? ${#scopes[$hash]} : max_scope )) | 
					
						
							|  |  |  |   done | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |   ##* Formatting functions | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Format the hash according to output format | 
					
						
							|  |  |  |   # If no parameter is passed, assume it comes from `$hash` | 
					
						
							|  |  |  |   function fmt:hash { | 
					
						
							|  |  |  |     #* Uses $hash from outer scope | 
					
						
							|  |  |  |     local hash="${1:-$hash}" | 
					
						
							|  |  |  |     case "$output" in | 
					
						
							| 
									
										
										
										
											2021-10-26 20:54:39 +02:00
										 |  |  |     raw) printf '%s' "$hash" ;; | 
					
						
							| 
									
										
										
										
											2023-03-31 18:49:59 +13:00
										 |  |  |     text) | 
					
						
							|  |  |  |       local text="\e[33m$hash\e[0m"; # red | 
					
						
							|  |  |  |       if supports_hyperlinks; then | 
					
						
							|  |  |  |         printf "\e]8;;%s\a%s\e]8;;\a" "https://github.com/ohmyzsh/ohmyzsh/commit/$hash" $text; | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         echo $text; | 
					
						
							|  |  |  |       fi ;; | 
					
						
							| 
									
										
										
										
											2022-11-27 17:54:24 +08:00
										 |  |  |     md) printf '[`%s`](https://github.com/ohmyzsh/ohmyzsh/commit/%s)' "$hash" "$hash" ;; | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     esac | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Format headers according to output format | 
					
						
							|  |  |  |   # Levels 1 to 2 are considered special, the rest are formatted | 
					
						
							|  |  |  |   # the same, except in md output format. | 
					
						
							|  |  |  |   function fmt:header { | 
					
						
							|  |  |  |     local header="$1" level="$2" | 
					
						
							|  |  |  |     case "$output" in | 
					
						
							|  |  |  |     raw) | 
					
						
							|  |  |  |       case "$level" in | 
					
						
							| 
									
										
										
										
											2021-10-26 20:54:39 +02:00
										 |  |  |       1) printf '%s\n%s\n\n' "$header" "$(printf '%.0s=' {1..${#header}})" ;; | 
					
						
							|  |  |  |       2) printf '%s\n%s\n\n' "$header" "$(printf '%.0s-' {1..${#header}})" ;; | 
					
						
							|  |  |  |       *) printf '%s:\n\n' "$header" ;; | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |       esac ;; | 
					
						
							|  |  |  |     text) | 
					
						
							|  |  |  |       case "$level" in | 
					
						
							| 
									
										
										
										
											2021-10-26 20:54:39 +02:00
										 |  |  |       1|2) printf '\e[1;4m%s\e[0m\n\n' "$header" ;; # bold, underlined | 
					
						
							|  |  |  |       *) printf '\e[1m%s:\e[0m\n\n' "$header" ;; # bold | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |       esac ;; | 
					
						
							| 
									
										
										
										
											2021-10-26 20:54:39 +02:00
										 |  |  |     md) printf '%s %s\n\n' "$(printf '%.0s#' {1..${level}})" "$header" ;; | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     esac | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function fmt:scope { | 
					
						
							|  |  |  |     #* Uses $scopes (A) and $hash from outer scope | 
					
						
							|  |  |  |     local scope="${1:-${scopes[$hash]}}" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # If no scopes, exit the function | 
					
						
							|  |  |  |     if [[ $max_scope -eq 0 ]]; then | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     fi | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Get how much padding is required for this scope | 
					
						
							| 
									
										
										
										
											2021-09-07 17:08:46 +02:00
										 |  |  |     local padding=0 | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     padding=$(( max_scope < ${#scope} ? 0 : max_scope - ${#scope} )) | 
					
						
							|  |  |  |     padding="${(r:$padding:: :):-}" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # If no scope, print padding and 3 spaces (equivalent to "[] ") | 
					
						
							|  |  |  |     if [[ -z "$scope" ]]; then | 
					
						
							|  |  |  |       printf "${padding}   " | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     fi | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Print [scope] | 
					
						
							|  |  |  |     case "$output" in | 
					
						
							| 
									
										
										
										
											2021-10-26 20:54:39 +02:00
										 |  |  |     raw|md) printf '[%s]%s ' "$scope" "$padding";; | 
					
						
							|  |  |  |     text) printf '[\e[38;5;9m%s\e[0m]%s ' "$scope" "$padding";; # red 9 | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     esac | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # If no parameter is passed, assume it comes from `$subjects[$hash]` | 
					
						
							|  |  |  |   function fmt:subject { | 
					
						
							|  |  |  |     #* Uses $subjects (A) and $hash from outer scope | 
					
						
							|  |  |  |     local subject="${1:-${subjects[$hash]}}" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Capitalize first letter of the subject | 
					
						
							|  |  |  |     subject="${(U)subject:0:1}${subject:1}" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case "$output" in | 
					
						
							| 
									
										
										
										
											2021-10-26 20:54:39 +02:00
										 |  |  |     raw) printf '%s' "$subject" ;; | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     # In text mode, highlight (#<issue>) and dim text between `backticks` | 
					
						
							| 
									
										
										
										
											2023-03-31 18:49:59 +13:00
										 |  |  |     text) | 
					
						
							|  |  |  |       if supports_hyperlinks; then | 
					
						
							|  |  |  |         sed -E $'s|#([0-9]+)|\e]8;;https://github.com/ohmyzsh/ohmyzsh/issues/\\1\a\e[32m#\\1\e[0m\e]8;;\a|g' <<< "$subject" | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         sed -E $'s|#([0-9]+)|\e[32m#\\1\e[0m|g;s|`([^`]+)`|`\e[2m\\1\e[0m`|g' <<< "$subject" | 
					
						
							|  |  |  |       fi ;; | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     # In markdown mode, link to (#<issue>) issues | 
					
						
							|  |  |  |     md) sed -E 's|#([0-9]+)|[#\1](https://github.com/ohmyzsh/ohmyzsh/issues/\1)|g' <<< "$subject" ;; | 
					
						
							|  |  |  |     esac | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function fmt:type { | 
					
						
							|  |  |  |     #* Uses $type from outer scope | 
					
						
							|  |  |  |     local type="${1:-${TYPES[$type]:-${(C)type}}}" | 
					
						
							|  |  |  |     [[ -z "$type" ]] && return 0 | 
					
						
							|  |  |  |     case "$output" in | 
					
						
							| 
									
										
										
										
											2021-10-26 20:54:39 +02:00
										 |  |  |     raw|md) printf '%s: ' "$type" ;; | 
					
						
							|  |  |  |     text) printf '\e[4m%s\e[24m: ' "$type" ;; # underlined | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     esac | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   ##* Section functions | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function display:version { | 
					
						
							|  |  |  |     fmt:header "$version" 2 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function display:breaking { | 
					
						
							|  |  |  |     (( $#breaking != 0 )) || return 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case "$output" in | 
					
						
							| 
									
										
										
										
											2021-10-26 20:54:39 +02:00
										 |  |  |     text) printf '\e[31m'; fmt:header "BREAKING CHANGES" 3 ;; | 
					
						
							| 
									
										
										
										
											2020-12-07 19:53:11 +01:00
										 |  |  |     raw) fmt:header "BREAKING CHANGES" 3 ;; | 
					
						
							| 
									
										
										
										
											2021-09-06 13:30:16 +02:00
										 |  |  |     md) fmt:header "BREAKING CHANGES ⚠" 3 ;; | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     esac | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-07 17:08:46 +02:00
										 |  |  |     local hash message | 
					
						
							|  |  |  |     local wrap_width=$(( (COLUMNS < 100 ? COLUMNS : 100) - 3 )) | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     for hash message in ${(kv)breaking}; do | 
					
						
							| 
									
										
										
										
											2021-09-07 17:13:46 +02:00
										 |  |  |       # Format the BREAKING CHANGE message by word-wrapping it at maximum 100 | 
					
						
							|  |  |  |       # characters (use $COLUMNS if smaller than 100) | 
					
						
							|  |  |  |       message="$(fmt -w $wrap_width <<< "$message")" | 
					
						
							|  |  |  |       # Display hash and scope in their own line, and then the full message with | 
					
						
							|  |  |  |       # blank lines as separators and a 3-space left padding | 
					
						
							|  |  |  |       echo " - $(fmt:hash) $(fmt:scope)\n\n$(fmt:subject "$message" | sed 's/^/   /')\n" | 
					
						
							| 
									
										
										
										
											2021-09-07 17:08:46 +02:00
										 |  |  |     done | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function display:type { | 
					
						
							|  |  |  |     local hash type="$1" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     local -a hashes | 
					
						
							| 
									
										
										
										
											2022-01-28 13:22:31 +01:00
										 |  |  |     hashes=(${(k)types[(R)$type]}) | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # If no commits found of type $type, go to next type | 
					
						
							|  |  |  |     (( $#hashes != 0 )) || return 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     fmt:header "${TYPES[$type]}" 3 | 
					
						
							|  |  |  |     for hash in $hashes; do | 
					
						
							|  |  |  |       echo " - $(fmt:hash) $(fmt:scope)$(fmt:subject)" | 
					
						
							|  |  |  |     done | sort -k3 # sort by scope | 
					
						
							|  |  |  |     echo | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function display:others { | 
					
						
							|  |  |  |     local hash type | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Commits made under types considered other changes | 
					
						
							|  |  |  |     local -A changes | 
					
						
							| 
									
										
										
										
											2022-01-28 13:22:31 +01:00
										 |  |  |     changes=(${(kv)types[(R)${(j:|:)OTHER_TYPES}]}) | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # If no commits found under "other" types, don't display anything | 
					
						
							|  |  |  |     (( $#changes != 0 )) || return 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     fmt:header "Other changes" 3 | 
					
						
							|  |  |  |     for hash type in ${(kv)changes}; do | 
					
						
							|  |  |  |       case "$type" in | 
					
						
							|  |  |  |       other) echo " - $(fmt:hash) $(fmt:scope)$(fmt:subject)" ;; | 
					
						
							|  |  |  |       *) echo " - $(fmt:hash) $(fmt:scope)$(fmt:type)$(fmt:subject)" ;; | 
					
						
							|  |  |  |       esac | 
					
						
							|  |  |  |     done | sort -k3 # sort by scope | 
					
						
							|  |  |  |     echo | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   ##* Release sections order | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Display version header | 
					
						
							|  |  |  |   display:version | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Display breaking changes first | 
					
						
							|  |  |  |   display:breaking | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Display changes for commit types in the order specified | 
					
						
							|  |  |  |   for type in $MAIN_TYPES; do | 
					
						
							|  |  |  |     display:type "$type" | 
					
						
							|  |  |  |   done | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Display other changes | 
					
						
							|  |  |  |   display:others | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function main { | 
					
						
							|  |  |  |   # $1 = until commit, $2 = since commit | 
					
						
							|  |  |  |   local until="$1" since="$2" | 
					
						
							| 
									
										
										
										
											2020-12-12 13:41:29 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   # $3 = output format (--text|--raw|--md) | 
					
						
							|  |  |  |   # --md:   uses markdown formatting | 
					
						
							|  |  |  |   # --raw:  outputs without style | 
					
						
							|  |  |  |   # --text: uses ANSI escape codes to style the output | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |   local output=${${3:-"--text"}#--*} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if [[ -z "$until" ]]; then | 
					
						
							|  |  |  |     until=HEAD | 
					
						
							|  |  |  |   fi | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if [[ -z "$since" ]]; then | 
					
						
							| 
									
										
										
										
											2020-12-12 13:41:29 +01:00
										 |  |  |     # If $since is not specified: | 
					
						
							|  |  |  |     # 1) try to find the version used before updating | 
					
						
							|  |  |  |     # 2) try to find the first version tag before $until | 
					
						
							|  |  |  |     since=$(command git config --get oh-my-zsh.lastVersion 2>/dev/null) || \
 | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     since=$(command git describe --abbrev=0 --tags "$until^" 2>/dev/null) || \
 | 
					
						
							|  |  |  |     unset since | 
					
						
							|  |  |  |   elif [[ "$since" = --all ]]; then | 
					
						
							|  |  |  |     unset since | 
					
						
							|  |  |  |   fi | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Commit classification arrays | 
					
						
							| 
									
										
										
										
											2022-01-28 13:22:31 +01:00
										 |  |  |   local -A types subjects scopes breaking reverts | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |   local truncate=0 read_commits=0 | 
					
						
							| 
									
										
										
										
											2021-10-26 18:24:29 +02:00
										 |  |  |   local version tag | 
					
						
							|  |  |  |   local hash refs subject body | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   # Get the first version name: | 
					
						
							|  |  |  |   # 1) try tag-like version, or | 
					
						
							| 
									
										
										
										
											2022-01-13 17:46:09 +01:00
										 |  |  |   # 2) try branch name, or | 
					
						
							|  |  |  |   # 3) try name-rev, or | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |   # 4) try short hash | 
					
						
							|  |  |  |   version=$(command git describe --tags $until 2>/dev/null) \
 | 
					
						
							|  |  |  |     || version=$(command git symbolic-ref --quiet --short $until 2>/dev/null) \
 | 
					
						
							| 
									
										
										
										
											2022-01-13 17:46:09 +01:00
										 |  |  |     || version=$(command git name-rev --no-undefined --name-only --exclude="remotes/*" $until 2>/dev/null) \
 | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     || version=$(command git rev-parse --short $until 2>/dev/null) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-26 18:24:29 +02:00
										 |  |  |   # Get commit list from $until commit until $since commit, or until root commit if $since is unset | 
					
						
							|  |  |  |   local range=${since:+$since..}$until | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Git log options | 
					
						
							|  |  |  |   # -z:             commits are delimited by null bytes | 
					
						
							|  |  |  |   # --format:       [7-char hash]<field sep>[ref names]<field sep>[subject]<field sep>[body] | 
					
						
							|  |  |  |   # --abbrev=7:     force commit hashes to be 7 characters long | 
					
						
							|  |  |  |   # --no-merges:    merge commits are omitted | 
					
						
							| 
									
										
										
										
											2021-10-26 18:26:02 +02:00
										 |  |  |   # --first-parent: commits from merged branches are omitted | 
					
						
							| 
									
										
										
										
											2021-10-26 18:24:29 +02:00
										 |  |  |   local SEP="0mZmAgIcSeP" | 
					
						
							|  |  |  |   local -a raw_commits | 
					
						
							| 
									
										
										
										
											2022-01-10 19:39:05 +01:00
										 |  |  |   raw_commits=(${(0)"$(command git -c log.showSignature=false log -z \
 | 
					
						
							| 
									
										
										
										
											2021-10-26 18:24:29 +02:00
										 |  |  |     --format="%h${SEP}%D${SEP}%s${SEP}%b" --abbrev=7 \
 | 
					
						
							| 
									
										
										
										
											2021-10-26 18:26:02 +02:00
										 |  |  |     --no-merges --first-parent $range)"})
 | 
					
						
							| 
									
										
										
										
											2021-10-26 18:24:29 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   local raw_commit | 
					
						
							|  |  |  |   local -a raw_fields | 
					
						
							|  |  |  |   for raw_commit in $raw_commits; do | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     # Truncate list on versions with a lot of commits | 
					
						
							| 
									
										
										
										
											2021-10-26 18:26:02 +02:00
										 |  |  |     if [[ -z "$since" ]] && (( ++read_commits > 35 )); then | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |       truncate=1 | 
					
						
							|  |  |  |       break | 
					
						
							|  |  |  |     fi | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-26 18:24:29 +02:00
										 |  |  |     # Read the commit fields (@ is needed to keep empty values) | 
					
						
							| 
									
										
										
										
											2021-10-27 10:12:23 +02:00
										 |  |  |     eval "raw_fields=(\"\${(@ps:$SEP:)raw_commit}\")" | 
					
						
							| 
									
										
										
										
											2021-10-26 18:24:29 +02:00
										 |  |  |     hash="${raw_fields[1]}" | 
					
						
							|  |  |  |     refs="${raw_fields[2]}" | 
					
						
							|  |  |  |     subject="${raw_fields[3]}" | 
					
						
							|  |  |  |     body="${raw_fields[4]}" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |     # If we find a new release (exact tag) | 
					
						
							| 
									
										
										
										
											2021-10-26 18:24:29 +02:00
										 |  |  |     if [[ "$refs" = *tag:\ * ]]; then | 
					
						
							|  |  |  |       # Parse tag name (needs: setopt extendedglob) | 
					
						
							|  |  |  |       tag="${${refs##*tag: }%%,# *}" | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |       # Output previous release | 
					
						
							|  |  |  |       display-release | 
					
						
							|  |  |  |       # Reinitialize commit storage | 
					
						
							| 
									
										
										
										
											2022-01-28 13:22:31 +01:00
										 |  |  |       types=() | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |       subjects=() | 
					
						
							|  |  |  |       scopes=() | 
					
						
							|  |  |  |       breaking=() | 
					
						
							|  |  |  |       reverts=() | 
					
						
							|  |  |  |       # Start work on next release | 
					
						
							|  |  |  |       version="$tag" | 
					
						
							|  |  |  |       read_commits=1 | 
					
						
							|  |  |  |     fi | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-26 18:24:29 +02:00
										 |  |  |     parse-commit "$hash" "$subject" "$body" | 
					
						
							| 
									
										
										
										
											2020-11-01 00:26:03 +01:00
										 |  |  |   done | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   display-release | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (( truncate )); then | 
					
						
							|  |  |  |     echo " ...more commits omitted" | 
					
						
							|  |  |  |     echo | 
					
						
							|  |  |  |   fi | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Use raw output if stdout is not a tty | 
					
						
							|  |  |  | if [[ ! -t 1 && -z "$3" ]]; then | 
					
						
							|  |  |  |   main "$1" "$2" --raw | 
					
						
							|  |  |  | else | 
					
						
							|  |  |  |   main "$@" | 
					
						
							|  |  |  | fi |