-
-
Save OleksandrKucherenko/9fb14f81a29b46886ccd63b774c5959f to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash | |
# shellcheck disable=SC2155 | |
## Copyright (C) 2017, Oleksandr Kucherenko | |
## Last revisit: 2023-09-30 | |
## Version: 2.0.2 | |
## License: MIT | |
## Fix: 2023-10-01, prefix for initial INIT_VERSION was not applied | |
## Fix: 2023-10-01, correct extraction of latest tag that match version pattern | |
## Added: 2023-09-30, @mrares prefix modification implemented | |
## Bug fixes: 2021-06-09, @slmingol changes applied | |
# For help: | |
# ./version-up.sh --help | |
# For developer / references: | |
# https://ryanstutorials.net/bash-scripting-tutorial/bash-functions.php | |
# http://tldp.org/LDP/abs/html/comparison-ops.html | |
# https://misc.flogisoft.com/bash/tip_colors_and_formatting | |
## display help | |
function help() { | |
echo 'usage: ./version-up.sh [-r|--release] [-a|--alpha] [-b|--beta] [-c|--release-candidate]' | |
echo ' [-m|--major] [-i|--minor] [-p|--patch] [-e|--revision] [-g|--git-revision]' | |
echo ' [--prefix root|sub-folder|any] [--stay] [--default] [--help]' | |
echo '' | |
echo 'Switches:' | |
echo ' --release switch stage to release, no suffix, -r' | |
echo ' --alpha switch stage to alpha, -a' | |
echo ' --beta switch stage to beta, -b' | |
echo ' --release-candidate switch stage to rc, -c' | |
echo ' --major increment MAJOR version part, -m' | |
echo ' --minor increment MINOR version part, -i' | |
echo ' --patch increment PATCH version part, -p' | |
echo ' --revision increment REVISION version part, -e' | |
echo ' --git-revision use git revision number as a revision part, -g' | |
echo ' --stay compose version.properties but do not do any increments, -s' | |
echo ' --default increment last found part of version, keeping the stage. Increment applied up to MINOR part.' | |
echo ' --apply run GIT command to apply version upgrade' | |
echo ' --prefix provide tag prefix or use on of the strategies: root, sub-folder (default), any_string' | |
echo '' | |
echo 'Version: [PREFIX]MAJOR.MINOR[.PATCH[.REVISION]][-STAGE]' | |
echo '' | |
echo 'Reference:' | |
echo ' https://semver.org/' | |
echo '' | |
echo 'Versions priority:' | |
echo ' 1.0.0-alpha < 1.0.0-beta < 1.0.0-rc < 1.0.0' | |
exit 0 | |
} | |
## find the monorepo root folder | |
function monorepo_root() { | |
# Navigate up from the script directory until we find the .git sub-folder to determine the monorepo root | |
local monorepoRootDir=$( | |
dir="$(dirname "${BASH_SOURCE[0]}")" | |
while [[ "$dir" != '/' && ! -d "$dir/.git" ]]; do dir="$(dirname "$dir")"; done | |
echo "$dir" | |
) | |
echo "$monorepoRootDir" | |
} | |
# https://stackoverflow.com/a/67449155 | |
function get_relative_path() { | |
local targetFilename=$(basename "$1") | |
local targetFolder=$(cd "$(dirname "$1")" && pwd) # absolute target folder path | |
local currentFolder=$(cd "$2" && pwd) # absolute source folder | |
local result=. | |
while [ "$currentFolder" != "$targetFolder" ]; do | |
if [[ "$targetFolder" =~ "$currentFolder"* ]]; then | |
local pointSegment=${targetFolder#${currentFolder}} | |
result=$result/${pointSegment#/} | |
break | |
fi | |
result="$result"/.. | |
currentFolder=$(dirname "$currentFolder") | |
done | |
result=$result/$targetFilename | |
echo "${result#./}" | |
} | |
## get monorepo sub-folder | |
function prefix_sub_folder() { | |
local tmpFileName="temp.file" | |
local repoDir=$(realpath "$(monorepo_root)") | |
local relativePath=$(get_relative_path "$(pwd)/$tmpFileName" "$repoDir") | |
# expected / at the end | |
echo "${relativePath/$tmpFileName/}" | |
} | |
## resolve income parameter into prefix | |
function prefix_strategy() { | |
local strategy=${1:-"sub-folder"} | |
local resolution="" | |
if [[ "$strategy" == "root" ]]; then | |
resolution="" | |
elif [[ "$strategy" == "sub-folder" ]]; then | |
resolution=$(prefix_sub_folder) | |
else | |
resolution="$strategy" | |
fi | |
echo "$resolution" | |
} | |
## calculate the prefix based on the strategy | |
function use_prefix() { | |
PREFIX=$(prefix_strategy "$1") | |
echo "Tag prefix: '$PREFIX'" | |
} | |
## get the highest version tag for all branches | |
function highest_tag() { | |
local gitTag=$(git tag --list 2>/dev/null | sort -V | tail -n1 2>/dev/null) | |
echo "$gitTag" | |
} | |
## extract current branch name | |
function current_branch() { | |
## expected: heads/{branch_name} | |
## expected: {branch_name} | |
local gitBranch=$(git rev-parse --abbrev-ref HEAD | cut -d"/" -f2) | |
echo "$gitBranch" | |
} | |
## get latest/head commit hash number | |
function head_hash() { | |
local commitHash=$(git rev-parse --verify HEAD) | |
echo "$commitHash" | |
} | |
## extract tag commit hash code, tag name provided by argument | |
function tag_hash() { | |
local tagHash=$(git log -1 --format=format:"%H" "$1" 2>/dev/null | tail -n1) | |
echo "$tagHash" | |
} | |
## extract prefix argument before the actual parsing of flags is done | |
function preparse_prefix_argument() { | |
local args=("$@") | |
local resolved_prefix=$(prefix_strategy) | |
# if --prefix provided, then required filtering of the tags by provided prefix pattern | |
if [[ "${args[*]}" =~ "--prefix" ]]; then | |
local prefix=$(echo "${args[*]}" | sed 's/.*--prefix \([^ ]*\).*/\1/') | |
resolved_prefix=$(prefix_strategy "$prefix") | |
fi | |
echo "$resolved_prefix" | |
} | |
## get latest tag in specified branch | |
# shellcheck disable=SC2001 | |
function latest_tag() { | |
local resolved_prefix=$(preparse_prefix_argument "$@") | |
# extract from git latest tag that started from number (OR from prefix and number) | |
local tag=$(git describe --tags --abbrev=0 --match="${resolved_prefix}[0-9]*" 2>/dev/null) | |
echo "$tag" | |
} | |
## get latest revision number | |
function latest_revision() { | |
local gitRevision=$(git rev-list --count HEAD 2>/dev/null) | |
echo "$gitRevision" | |
} | |
## parse first PART of the tage, extract PREFIX if any provided | |
# shellcheck disable=SC2001 | |
function parse_first() { | |
# extract into PREFIX variable all non digits chars from the beginning of the PARTS[0] | |
local prefix=$(echo "${PARTS[0]}" | sed 's#\([^0-9]*\)\(.*\)#\1#') | |
# leave only digits in the PARTS[0] | |
local clean_part=$(echo "${PARTS[0]}" | sed 's#\([^0-9]*\)\(.*\)#\2#') | |
PREFIX=$prefix | |
PARTS[0]=$clean_part | |
} | |
## parse last found tag, extract it PARTS | |
function parse_last() { | |
local position=$(($1 - 1)) | |
# two parts found only | |
# shellcheck disable=SC2206 | |
local segments=(${PARTS[$position]//-/ }) # split by - into array of strings | |
#echo ${segments[@]}, size: ${#segments} | |
# found NUMBER | |
PARTS[$position]=${segments[0]} | |
#echo ${PARTS[@]} | |
# found SUFFIX | |
if [[ ${#segments} -ge 1 ]]; then | |
PARTS[4]=${segments[1],,} #lowercase | |
#echo ${PARTS[@]}, ${SUBS[@]} | |
fi | |
} | |
## increment REVISION part, don't touch STAGE | |
function increment_revision() { | |
PARTS[3]=$((PARTS[3] + 1)) | |
IS_DIRTY=1 | |
} | |
## increment PATCH part, reset all others lower PARTS, don't touch STAGE | |
function increment_patch() { | |
PARTS[2]=$((PARTS[2] + 1)) | |
PARTS[3]=0 | |
IS_DIRTY=1 | |
} | |
## increment MINOR part, reset all others lower PARTS, don't touch STAGE | |
function increment_minor() { | |
PARTS[1]=$((PARTS[1] + 1)) | |
PARTS[2]=0 | |
PARTS[3]=0 | |
IS_DIRTY=1 | |
} | |
## increment MAJOR part, reset all others lower PARTS, don't touch STAGE | |
function increment_major() { | |
PARTS[0]=$((PARTS[0] + 1)) | |
PARTS[1]=0 | |
PARTS[2]=0 | |
PARTS[3]=0 | |
IS_DIRTY=1 | |
} | |
## increment the number only of last found PART: REVISION --> PATCH --> MINOR. don't touch STAGE | |
function increment_last_found() { | |
if [[ "${#PARTS[3]}" == 0 || "${PARTS[3]}" == "0" ]]; then | |
if [[ "${#PARTS[2]}" == 0 || "${PARTS[2]}" == "0" ]]; then | |
increment_minor | |
else | |
increment_patch | |
fi | |
else | |
increment_revision | |
fi | |
# stage part is not EMPTY | |
if [[ "${#PARTS[4]}" != 0 ]]; then | |
IS_SHIFT=1 | |
fi | |
} | |
## compose version from PARTS | |
function compose() { | |
local major="${PARTS[0]}" | |
local minor=".${PARTS[1]}" | |
local patch=".${PARTS[2]}" | |
local revision=".${PARTS[3]}" | |
local suffix="-${PARTS[4]}" | |
if [[ "${#patch}" == 1 ]]; then # if empty {patch} | |
patch="" | |
fi | |
if [[ "${#revision}" == 1 ]]; then # if empty {revision} | |
revision="" | |
fi | |
if [[ "${PARTS[3]}" == "0" ]]; then # if revision is ZERO | |
revision="" | |
fi | |
# shrink patch and revision | |
if [[ -z "${revision// /}" ]]; then | |
if [[ "${PARTS[2]}" == "0" ]]; then | |
patch="" | |
fi | |
else # revision is not EMPTY | |
if [[ "${#patch}" == 0 ]]; then | |
patch=".0" | |
fi | |
fi | |
# remove suffix if we don't have alpha/beta/rc | |
if [[ "${#suffix}" == 1 ]]; then | |
suffix="" | |
fi | |
echo "${PREFIX}${major}${minor}${patch}${revision}${suffix}" #full format | |
} | |
## print error message about conflict with existing tag and proposed tag | |
function error_conflict_tag() { | |
local red=$(tput setaf 1) | |
local end=$(tput sgr0) | |
local yellow=$(tput setaf 3) | |
echo -e "${red}ERROR:${end} " | |
echo -e "${red}ERROR:${end} Found conflict with existing tag ${yellow}$(compose)${end} / $PROPOSED_HASH" | |
echo -e "${red}ERROR:${end} Only manual resolving is possible now." | |
echo -e "${red}ERROR:${end} " | |
echo -e "${red}ERROR:${end} To Resolve try to add --revision or --patch modifier." | |
echo -e "${red}ERROR:${end} " | |
echo "" | |
} | |
## print help message how to apply changes manually | |
function help_manual_apply() { | |
echo 'To apply changes manually execute the command(s):' | |
echo -e "\033[90m" | |
echo " git tag $(compose)" | |
echo " git push origin $(compose)" | |
echo -e "\033[0m" | |
} | |
## save all support information into version.properties file | |
function publish_version_file() { | |
echo "# $(date)" >${VERSION_FILE} | |
{ | |
echo "snapshot.version=$(compose)" | |
echo "snapshot.lasttag=$TAG" | |
echo "snapshot.revision=$REVISION" | |
echo "snapshot.hightag=$TOP_TAG" | |
echo "snapshot.branch=$BRANCH" | |
echo '# end of file' | |
} >>"${VERSION_FILE}" | |
} | |
## apply changes to GIT repository, local changes only | |
function apply_git_changes() { | |
echo '' | |
echo "Applying git repository version up... no push, only local tag assignment!" | |
echo '' | |
git tag "$(compose)" | |
# confirm that tag applied | |
git --no-pager log \ | |
--pretty=format:"%h%x09%Cblue%cr%Cgreen%x09%an%Creset%x09%s%Cred%d%Creset" \ | |
-n 2 --date=short | nl -w2 -s" " | |
echo '' | |
echo '' | |
} | |
## print current state of the repository | |
function report_current_state() { | |
# do we have any GIT tag for parsing?! | |
echo "" | |
if [[ -z "${TAG// /}" ]]; then | |
TAG=$INIT_VERSION | |
echo "No tags found." | |
else | |
echo "Found tag: $TAG in branch '$BRANCH'" | |
fi | |
# print current revision number based on number of commits | |
echo "Current Revision: $REVISION" | |
echo "Current Branch : $BRANCH" | |
echo "Repository Dir : \"$(realpath "$(monorepo_root)")\"" | |
echo "Current Folder : \"$(prefix_sub_folder)\"" | |
echo "" | |
} | |
PREFIX=$(preparse_prefix_argument "$@") | |
# initial version used for repository without tags | |
INIT_VERSION="${PREFIX}0.0.0.0-alpha" | |
# do GIT data extracting, globals | |
TAG=$(latest_tag "$@") | |
REVISION=$(latest_revision) | |
BRANCH=$(current_branch) | |
TOP_TAG=$(highest_tag) | |
TAG_HASH=$(tag_hash "$TAG") | |
HEAD_HASH=$(head_hash) | |
PROPOSED_HASH="" | |
VERSION_FILE=version.properties | |
report_current_state | |
# if tag and branch commit hashes are different, then print info about that | |
#echo $HEAD_HASH vs $TAG_HASH | |
# shellcheck disable=SC2199 | |
if [[ "$@" == "" ]]; then | |
if [[ "$TAG_HASH" == "$HEAD_HASH" ]]; then | |
echo "Tag $TAG and HEAD are aligned. We will stay on the TAG version." | |
echo "" | |
NO_ARGS_VALUE='--stay' | |
else | |
PATTERN="^[0-9]+.[0-9]+(.[0-9]+)*(-(alpha|beta|rc))*$" | |
if [[ "$BRANCH" =~ $PATTERN ]]; then | |
echo "Detected version branch '$BRANCH'. We will auto-increment the last version PART." | |
echo "" | |
NO_ARGS_VALUE='--default' | |
else | |
echo "Detected branch name '$BRANCH' than does not match version pattern. We will increase MINOR." | |
echo "" | |
NO_ARGS_VALUE='--minor' | |
fi | |
fi | |
fi | |
# | |
# [PREFIX]{MAJOR}.{MINOR}[.{PATCH}[.{REVISION}][-(.*)] | |
# | |
# Suffix: alpha, beta, rc | |
# No Suffix --> {NEW_VERSION}-alpha | |
# alpha --> beta | |
# beta --> rc | |
# rc --> {VERSION} | |
# | |
# shellcheck disable=SC2206 | |
PARTS=(${TAG//./ }) | |
parse_first | |
parse_last ${#PARTS[@]} # array size as argument | |
#echo ${PARTS[@]} | |
# if no parameters than emulate --default parameter | |
# shellcheck disable=SC2199 | |
if [[ "$@" == "" ]]; then | |
# shellcheck disable=SC2086 | |
set -- ${NO_ARGS_VALUE} | |
fi | |
# parse input parameters | |
for i in "$@"; do | |
key="$i" | |
case $key in | |
-a | --alpha) # switched to ALPHA | |
PARTS[4]="alpha" | |
IS_SHIFT=1 | |
;; | |
-b | --beta) # switched to BETA | |
PARTS[4]="beta" | |
IS_SHIFT=1 | |
;; | |
-c | --release-candidate) # switched to RC | |
PARTS[4]="rc" | |
IS_SHIFT=1 | |
;; | |
-r | --release) # switched to RELEASE | |
PARTS[4]="" | |
IS_SHIFT=1 | |
;; | |
-p | --patch) # increment of PATCH | |
increment_patch | |
;; | |
-e | --revision) # increment of REVISION | |
increment_revision | |
;; | |
-g | --git-revision) # use git revision number as a revision part§ | |
PARTS[3]=$((REVISION)) | |
IS_DIRTY=1 | |
;; | |
-i | --minor) # increment of MINOR by default | |
increment_minor | |
;; | |
--default) # stay on the same stage, but increment only last found PART of version code | |
increment_last_found | |
;; | |
-m | --major) # increment of MAJOR | |
increment_major | |
;; | |
-s | --stay) # extract version info | |
IS_DIRTY=1 | |
NO_APPLY_MSG=1 | |
;; | |
--prefix) # version tag prefix provided | |
use_prefix "$2" | |
shift # expected one more argument with prefix name | |
;; | |
--apply) | |
DO_APPLY=1 | |
;; | |
-h | --help) | |
help | |
;; | |
esac | |
shift | |
done | |
# detected shift, but no increment | |
if [[ "$IS_SHIFT" == "1" ]]; then | |
# temporary disable stage shift | |
stage=${PARTS[4]} | |
PARTS[4]='' | |
# detect first run on repository, INIT_VERSION was used | |
if [[ "$(compose)" == "0.0" ]]; then | |
increment_minor | |
fi | |
PARTS[4]=$stage | |
fi | |
# no increment applied yet and no shift of state, do minor increase | |
if [[ "$IS_DIRTY$IS_SHIFT" == "" ]]; then | |
increment_minor | |
fi | |
# instruct user how to apply new TAG | |
echo -e "Proposed TAG: \033[32m$(compose)\033[0m" | |
echo '' | |
# is proposed tag in conflict with any other TAG | |
PROPOSED_HASH=$(tag_hash "$(compose)") | |
if [[ "${#PROPOSED_HASH}" -gt 0 && "$NO_APPLY_MSG" == "" ]]; then | |
error_conflict_tag | |
fi | |
if [[ "$NO_APPLY_MSG" == "" ]]; then | |
help_manual_apply | |
fi | |
# compose version override file | |
if [[ "$TAG" == "$INIT_VERSION" ]]; then | |
TAG='0.0' | |
fi | |
publish_version_file | |
# should we apply the changes | |
if [[ "$DO_APPLY" == "1" ]]; then | |
apply_git_changes | |
fi | |
# | |
# Major logic of the script - "on each run script propose future version of the product". | |
# | |
# - if no tags on project --> propose '0.1-alpha' | |
# - do multiple build iterations until you become satisfied with result | |
# - run 'version-up.sh --apply' to save result in GIT | |
# |
Hi
Thank you very much for this script, I think you should make a dedicated repo...
I've a question: does it work with prefix on TAG? I noticed that the MAGIOR doesn't increase If it has a prefix. For example:
./version-up.sh -m Found tag: v2.14.1 in branch 'master' Current Revision: 192 Current Branch: master Proposed TAG: 1.0 To apply changes manually execute the command(s): git tag 1.0 git push origin 1.0
If I understand correctly, expected output should be:
Proposed TAG: v3.0
Expected tag 2.14.1
but was v2.14.1
... you need to adjust the script for your version tag pattern.
You may check: https://stackoverflow.com/a/5719854/349681 - How to rename git tag
Thank you very much. Very nice job indeed.
Do you have any thoughts on the supported versioning scheme?
I'm considering expanding the script to support "official" Semantic Versioning 2.0.0 (as found on https://semver.org/spec/v2.0.0.html).
... or at least supporting the versioning scheme we use (v1.2.3-alpha.43 should be able to up into v1.2.3-alpha.44) - which doesn't seem to be supported at the moment.
Also please consider turning this gist into a repo :D
Thank you for this script. I added a couple of echo ''
to the apply condition to cleanup the output displayed when running the git --no-pager log ...
cmds. Otherwise my prompt started right at the end of the last log msg it displayed.
...
...
# should we apply the changes
if [[ "$DO_APPLY" == "1" ]]; then
echo ''
echo "Applying git repository version up... no push, only local tag assignment!"
echo ''
git tag $(compose)
# confirm that tag applied
git --no-pager log --pretty=format:"%h%x09%Cblue%cr%Cgreen%x09%an%Creset%x09%s%Cred%d%Creset" -n 2 --date=short | nl -w2 -s" "
echo ''
echo ''
fi
I suspect this might be a bug here where you're calculating the highest tag value:
## get highest version tag for all branches
function highest_tag(){
local TAG=$(git tag --list 2>/dev/null | tail -n1 2>/dev/null)
echo "$TAG"
}
I suspect this should be like so:
## get highest version tag for all branches
function highest_tag(){
local TAG=$(git tag --list 2>/dev/null | tail -n1 2>/dev/null | sort -V)
echo "$TAG"
}
For e.g.:
$ git tag --list | tail -10
0.0.70
0.0.71
0.0.72
0.0.73
0.0.74
0.0.75
0.0.76
0.0.77
0.0.8
0.0.9
But with my approach:
$ git tag --list | tail -10 | sort -V
0.0.8
0.0.9
0.0.70
0.0.71
0.0.72
0.0.73
0.0.74
0.0.75
0.0.76
0.0.77
I suspect this might be a bug here where you're calculating the highest tag value:
Thanks @slmingol for catching it. I will update the script shortly.
Script updated.
hi @OleksandrKucherenko can you create a parameter to output just the tag to be used ?
Im try in Makefile create a version to build my dockerimage with this tag but I cant with regular output of this script so I tried this bellow
VERSION=$(shell ./version-up.sh --patch --release | grep "TAG" | awk '{split($0, a, ":"); print a[2]}' | sed 's/\x1b\[[0-9;]*m//g' | sed -e 's/^[[:space:]]*//')
version: ## output to version
@echo $(VERSION)
make version
awk: syntax error at source line 1
context is
>>> {split(, <<<
awk: illegal statement at source line 1
awk: illegal statement at source line 1
Can you include a parameter to output only the calculated tag ?
I use a monorepo and so have different tags for different services, is there some hack I could use to make this work for me? (effectively I'd like to filter tags by prefix before using this tool)
@mrares let's assume you want to support tags in the following pattern:
# We have a workspace with subfolders, use this to print tree of the project:
# find . | sed -e "s/[^-][^\/]*\// |/g" -e "s/|\([^ ]\)/|-\1/"
#
.
|-.git
| |-branches
| |-info
| |-hooks
| |-refs
| |-objects
| |-logs
|-.husky
|-.idea
|-.scripts
|-.vscode
|-.yarn
| |-plugins
| |-releases
| |-sdks
|-clis
| |-gpt
|-packages
| |-arguments
| |-configuration
| |-gc
| |-telemetry
# $(pwd)/${version}
git tag packages/telemetry/v1.0.0
# something like this
export expected_tag="$(realpath --relative-to=${workspace_root_folder} "$(pwd)")/${version_tag}"
Can you confirm that this is a good way for you?
In my case this would be tagged telemetry-v1.0.0 but I can see the logic behind what you're proposing.
In my opinion this can be done by specifying a prefix to the command, so it ignores any tags that are not prefixed the same way (currently it searches for the latest tag, which will have the prefix let's say "telemetry"), if I could specify --prefix "arguments" then it will look for the latest tag prefixed arguments, and then operate further from there (it would generate the next tag for that)
@mrares just a hint, try chatgpt for updating the script - it solve such kind of tasks without any issues
@mrares just a hint, try chatgpt for updating the script - it solve such kind of tasks without any issues
?!
@mrares Updated version with the --prefix
approach implementation
Preview
prefix provided as user defined custom string:
sub-folder strategy in use: (default settings or can be forced by --prefix sub-folder
)
script try to propose tag based on sub-folder path (relative path to monorepo root dir):
force root directory logic:
version 2.0.0 released
@rafilkmp3 @mrares @slmingol @rasmusskovdk @vlauciani @nicknezis @sadortun @thuitaw @Br3nda
Changes:
- support for monorepo added
- sub-folder used as version tag prefix by default, developer should be in the module folder when calling the
version-up.sh
script --prefix
flag allows to change the strategy 'root' - use repo root folder logic, 'sub-folder' (default), any-text - prefix provided by developer- refactoring:
- more functionality placed into functions
- local variables are in lower/camel case
- global variables in UPPER case
- shellcheck proposed/found warnings/errors fixed
- tested on MacOs only
Known issues:
- empty prefix or root strategy may select the wrong tag for processing, used hungry wildcard that select 'telemetry-0.0.1' instead of '1.0.0.0' tag from images above... any suggestions how fix are welcome 🙏
@OleksandrKucherenko Thank you, --prefix
now works perfectly for my needs
migrated to GitHub Project - https://github.com/OleksandrKucherenko/e-bash
Hi
Thank you very much for this script, I think you should make a dedicated repo...
I've a question: does it work with prefix on TAG? I noticed that the MAGIOR doesn't increase If it has a prefix. For example:
If I understand correctly, expected output should be:
Proposed TAG: v3.0