github.com/akamai/AkamaiOPEN-edgegrid-golang/v8@v8.1.0/scripts/semtag (about) 1 #!/usr/bin/env bash 2 # this file has semantic versioning script from https://github.com/pnikosis/semtag 3 4 PROG=semtag 5 PROG_VERSION="v0.1.0" 6 7 SEMVER_REGEX="^v?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(\-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$" 8 IDENTIFIER_REGEX="^\-([0-9A-Za-z-]+)\.([0-9A-Za-z-]+)*$" 9 10 # Global variables 11 FIRST_VERSION="v0.0.0" 12 finalversion=$FIRST_VERSION 13 lastversion=$FIRST_VERSION 14 hasversiontag="false" 15 scope="patch" 16 displayonly="false" 17 forcetag="false" 18 forcedversion= 19 versionname= 20 identifier= 21 22 HELP="\ 23 Usage: 24 $PROG 25 $PROG getlast 26 $PROG getfinal 27 $PROG (final|alpha|beta|candidate) [-s <scope> (major|minor|patch|auto) | -o] 28 $PROG --help 29 $PROG --version 30 Options: 31 -s The scope that must be increased, can be major, minor or patch. 32 The resulting version will match X.Y.Z(-PRERELEASE)(+BUILD) 33 where X, Y and Z are positive integers, PRERELEASE is an optionnal 34 string composed of alphanumeric characters describing if the build is 35 a release candidate, alpha or beta version, with a number. 36 BUILD is also an optional string composed of alphanumeric 37 characters and hyphens. 38 Setting the scope as 'auto', the script will chose the scope between 39 'minor' and 'patch', depending on the amount of lines added (<10% will 40 choose patch). 41 -v Specifies manually the version to be tagged, must be a valid semantic version 42 in the format X.Y.Z where X, Y and Z are positive integers. 43 -o Output the version only, shows the bumped version, but doesn't tag. 44 -f Forces to tag, even if there are unstaged or uncommited changes. 45 Commands: 46 --help Print this help message. 47 --version Prints the program's version. 48 get Returns both current final version and last tagged version. 49 getlast Returns the latest tagged version. 50 getfinal Returns the latest tagged final version. 51 getcurrent Returns the current version, based on the latest one, if there are uncommited or 52 unstaged changes, they will be reflected in the version, adding the number of 53 pending commits, current branch and commit hash. 54 final Tags the current build as a final version, this only can be done on the master branch. 55 candidate Tags the current build as a release candidate, the tag will contain all 56 the commits from the last final version. 57 alpha Tags the current build as an alpha version, the tag will contain all 58 the commits from the last final version. 59 beta Tags the current build as a beta version, the tag will contain all 60 the commits from the last final version." 61 62 # Commands and options 63 ACTION="getlast" 64 ACTION="$1" 65 shift 66 67 # We get the parameters 68 while getopts "v:s:of" opt; do 69 case $opt in 70 v) 71 forcedversion="$OPTARG" 72 ;; 73 s) 74 scope="$OPTARG" 75 ;; 76 o) 77 displayonly="true" 78 ;; 79 f) 80 forcetag="true" 81 ;; 82 \?) 83 echo "Invalid option: -$OPTARG" >&2 84 exit 1 85 ;; 86 :) 87 echo "Option -$OPTARG requires an argument." >&2 88 exit 1 89 ;; 90 esac 91 done 92 93 # Gets a string with the version and returns an array of maximum size of 5 with all the parts of the sematinc version 94 # $1 The string containing the version in semantic format 95 # $2 The variable to store the result array: 96 # position 0: major number 97 # position 1: minor number 98 # position 2: patch number 99 # position 3: identifier (or prerelease identifier) 100 # position 4: build info 101 function explode_version { 102 local __version=$1 103 local __result=$2 104 if [[ $__version =~ $SEMVER_REGEX ]] ; then 105 local __major=${BASH_REMATCH[1]} 106 local __minor=${BASH_REMATCH[2]} 107 local __patch=${BASH_REMATCH[3]} 108 local __prere=${BASH_REMATCH[4]} 109 local __build=${BASH_REMATCH[5]} 110 eval "$__result=(\"$__major\" \"$__minor\" \"$__patch\" \"$__prere\" \"$__build\")" 111 else 112 eval "$__result=" 113 fi 114 } 115 116 # Compare two versions and returns -1, 0 or 1 117 # $1 The first version to compare 118 # $2 The second version to compare 119 # $3 The variable where to store the result 120 function compare_versions { 121 local __first 122 local __second 123 explode_version $1 __first 124 explode_version $2 __second 125 local lv=$3 126 127 # Compares MAJOR, MINOR and PATCH 128 for i in 0 1 2; do 129 local __numberfirst=${__first[$i]} 130 local __numbersecond=${__second[$i]} 131 case $(($__numberfirst - $__numbersecond)) in 132 0) 133 ;; 134 -[0-9]*) 135 eval "$lv=-1" 136 return 0 137 ;; 138 [0-9]*) 139 eval "$lv=1" 140 return 0 141 ;; 142 esac 143 done 144 145 # Identifiers should compare with the ASCII order. 146 local __identifierfirst=${__first[3]} 147 local __identifiersecond=${__second[3]} 148 if [[ -n "$__identifierfirst" ]] && [[ -n "$__identifiersecond" ]]; then 149 if [[ "$__identifierfirst" > "$__identifiersecond" ]]; then 150 eval "$lv=1" 151 return 0 152 elif [[ "$__identifierfirst" < "$__identifiersecond" ]]; then 153 eval "$lv=-1" 154 return 0 155 fi 156 elif [[ -z "$__identifierfirst" ]] && [[ -n "$__identifiersecond" ]]; then 157 eval "$lv=1" 158 return 0 159 elif [[ -n "$__identifierfirst" ]] && [[ -z "$__identifiersecond" ]]; then 160 eval "$lv=-1" 161 return 0 162 fi 163 164 eval "$lv=0" 165 } 166 167 # Returns the last version of two 168 # $1 The first version to compare 169 # $2 The second version to compare 170 # $3 The variable where to store the last one 171 function get_latest_of_two { 172 local __first=$1 173 local __second=$2 174 local __result 175 local __latest=$3 176 compare_versions $__first $__second __result 177 case $__result in 178 0) 179 eval "$__latest=$__second" 180 ;; 181 -1) 182 eval "$__latest=$__second" 183 ;; 184 1) 185 eval "$__latest=$__first" 186 ;; 187 esac 188 } 189 190 # Assigns a 2 size array with the identifier, having the identifier at pos 0, and the number in pos 1 191 # $1 The identifier in the format -id.# 192 # $2 The vferiable where to store the 2 size array 193 function explode_identifier { 194 local __identifier=$1 195 local __result=$2 196 if [[ $__identifier =~ $IDENTIFIER_REGEX ]] ; then 197 local __id=${BASH_REMATCH[1]} 198 local __number=${BASH_REMATCH[2]} 199 if [[ -z "$__number" ]]; then 200 __number=1 201 fi 202 eval "$__result=(\"$__id\" \"$__number\")" 203 else 204 eval "$__result=" 205 fi 206 } 207 208 # Gets a list of tags and assigns the base and latest versions 209 # Receives an array with the tags containing the versions 210 # Assigns to the global variables finalversion and lastversion the final version and the latest version 211 function get_latest { 212 local __taglist=("$@") 213 local __tagsnumber=${#__taglist[@]} 214 local __current 215 case $__tagsnumber in 216 0) 217 finalversion=$FIRST_VERSION 218 lastversion=$FIRST_VERSION 219 ;; 220 1) 221 __current=${__taglist[0]} 222 explode_version $__current ver 223 if [ -n "$ver" ]; then 224 if [ -n "${ver[3]}" ]; then 225 finalversion=$FIRST_VERSION 226 else 227 finalversion=$__current 228 fi 229 lastversion=$__current 230 else 231 finalversion=$FIRST_VERSION 232 lastversion=$FIRST_VERSION 233 fi 234 ;; 235 *) 236 local __lastpos=$(($__tagsnumber-1)) 237 for i in $(seq 0 $__lastpos) 238 do 239 __current=${__taglist[i]} 240 explode_version ${__taglist[i]} ver 241 if [ -n "$ver" ]; then 242 if [ -z "${ver[3]}" ]; then 243 get_latest_of_two $finalversion $__current finalversion 244 get_latest_of_two $lastversion $finalversion lastversion 245 else 246 get_latest_of_two $lastversion $__current lastversion 247 fi 248 fi 249 done 250 ;; 251 esac 252 253 if git rev-parse -q --verify "refs/tags/$lastversion" >/dev/null; then 254 hasversiontag="true" 255 else 256 hasversiontag="false" 257 fi 258 } 259 260 # Gets the next version given the provided scope 261 # $1 The version that is going to be bumped 262 # $2 The scope to bump 263 # $3 The variable where to stoer the result 264 function get_next_version { 265 local __exploded 266 local __fromversion=$1 267 local __scope=$2 268 local __result=$3 269 explode_version $__fromversion __exploded 270 case $__scope in 271 major) 272 __exploded[0]=$((${__exploded[0]}+1)) 273 __exploded[1]=0 274 __exploded[2]=0 275 ;; 276 minor) 277 __exploded[1]=$((${__exploded[1]}+1)) 278 __exploded[2]=0 279 ;; 280 patch) 281 __exploded[2]=$((${__exploded[2]}+1)) 282 ;; 283 esac 284 285 eval "$__result=v${__exploded[0]}.${__exploded[1]}.${__exploded[2]}" 286 } 287 288 function bump_version { 289 ## First we try to get the next version based on the existing last one 290 if [ "$scope" == "auto" ]; then 291 get_scope_auto scope 292 fi 293 294 local __candidatefromlast=$FIRST_VERSION 295 local __explodedlast 296 explode_version $lastversion __explodedlast 297 if [[ -n "${__explodedlast[3]}" ]]; then 298 # Last version is not final 299 local __idlast 300 explode_identifier ${__explodedlast[3]} __idlast 301 302 # We get the last, given the desired id based on the scope 303 __candidatefromlast="v${__explodedlast[0]}.${__explodedlast[1]}.${__explodedlast[2]}" 304 if [[ -n "$identifier" ]]; then 305 local __nextid="$identifier.1" 306 if [ "$identifier" == "${__idlast[0]}" ]; then 307 # We target the same identifier as the last so we increase one 308 __nextid="$identifier.$(( ${__idlast[1]}+1 ))" 309 __candidatefromlast="$__candidatefromlast-$__nextid" 310 else 311 # Different identifiers, we make sure we are assigning a higher identifier, if not, we increase the version 312 __candidatefromlast="$__candidatefromlast-$__nextid" 313 local __comparedwithlast 314 compare_versions $__candidatefromlast $lastversion __comparedwithlast 315 if [ "$__comparedwithlast" == -1 ]; then 316 get_next_version $__candidatefromlast $scope __candidatefromlast 317 __candidatefromlast="$__candidatefromlast-$__nextid" 318 fi 319 fi 320 fi 321 fi 322 323 # Then we try to get the version based on the latest final one 324 local __candidatefromfinal=$FIRST_VERSION 325 get_next_version $finalversion $scope __candidatefromfinal 326 if [[ -n "$identifier" ]]; then 327 __candidatefromfinal="$__candidatefromfinal-$identifier.1" 328 fi 329 330 # Finally we compare both candidates 331 local __resultversion 332 local __result 333 compare_versions $__candidatefromlast $__candidatefromfinal __result 334 case $__result in 335 0) 336 __resultversion=$__candidatefromlast 337 ;; 338 -1) 339 __resultversion="$__candidatefromfinal" 340 ;; 341 1) 342 __resultversion=$__candidatefromlast 343 ;; 344 esac 345 346 eval "$1=$__resultversion" 347 } 348 349 function increase_version { 350 local __version= 351 352 if [ -z $forcedversion ]; then 353 bump_version __version 354 else 355 if [[ $forcedversion =~ $SEMVER_REGEX ]] ; then 356 compare_versions $forcedversion $lastversion __result 357 if [ $__result -le 0 ]; then 358 echo "Version can't be lower than last version: $lastversion" 359 exit 1 360 fi 361 else 362 echo "Non valid version to bump" 363 exit 1 364 fi 365 __version=$forcedversion 366 fi 367 368 if [ "$displayonly" == "true" ]; then 369 echo "$__version" 370 else 371 if [ "$forcetag" == "false" ]; then 372 check_git_dirty_status 373 fi 374 local __commitlist 375 if [ "$finalversion" == "$FIRST_VERSION" ] || [ "$hasversiontag" != "true" ]; then 376 __commitlist="$(git log --pretty=oneline | cat)" 377 else 378 __commitlist="$(git log --pretty=oneline $finalversion... | cat)" 379 fi 380 381 # If we are forcing a bump, we add bump to the commit list 382 if [[ -z $__commitlist && "$forcetag" == "true" ]]; then 383 __commitlist="bump" 384 fi 385 386 if [[ -z $__commitlist ]]; then 387 echo "No commits since the last final version, not bumping version" 388 else 389 if [[ -z $versionname ]]; then 390 versionname=$(date -u +"%Y-%m-%dT%H:%M:%SZ") 391 fi 392 local __message="$versionname 393 $__commitlist" 394 395 # We check we have info on the user 396 local __username=$(git config user.name) 397 if [ -z "$__username" ]; then 398 __username=$(id -u -n) 399 git config user.name $__username 400 fi 401 local __useremail=$(git config user.email) 402 if [ -z "$__useremail" ]; then 403 __useremail=$(hostname) 404 git config user.email "$__username@$__useremail" 405 fi 406 407 git tag -a $__version -m "$__message" 408 409 # If we have a remote, we push there 410 local __remotes=$(git remote) 411 if [[ -n $__remotes ]]; then 412 for __remote in $__remotes; do 413 git push $__remote $__version > /dev/null 414 if [ $? -eq 0 ]; then 415 echo "$__version pushed to $__remote" 416 else 417 echo "Error pushing the tag $__version to $__remote" 418 exit 1 419 fi 420 done 421 else 422 echo "$__version" 423 fi 424 fi 425 fi 426 } 427 428 function check_git_dirty_status { 429 local __repostatus= 430 get_work_tree_status __repostatus 431 432 if [ "$__repostatus" == "uncommitted" ]; then 433 echo "ERROR: You have uncommitted changes" 434 git status --porcelain 435 exit 1 436 fi 437 438 if [ "$__repostatus" == "unstaged" ]; then 439 echo "ERROR: You have unstaged changes" 440 git status --porcelain 441 exit 1 442 fi 443 } 444 445 # Get the total amount of lines of code in the repo 446 function get_total_lines { 447 local __empty_id="$(git hash-object -t tree /dev/null)" 448 local __changes="$(git diff --numstat $__empty_id | cat)" 449 local __added_deleted=$1 450 get_changed_lines "$__changes" $__added_deleted 451 } 452 453 # Get the total amount of lines of code since the provided tag 454 function get_sincetag_lines { 455 local __sincetag=$1 456 local __changes="$(git diff --numstat $__sincetag | cat)" 457 local __added_deleted=$2 458 get_changed_lines "$__changes" $__added_deleted 459 } 460 461 function get_changed_lines { 462 local __changes_numstat=$1 463 local __result=$2 464 IFS=$'\n' read -rd '' -a __changes_array <<<"$__changes_numstat" 465 local __diff_regex="^([0-9]+)[[:space:]]+([0-9]+)[[:space:]]+.+$" 466 467 local __total_added=0 468 local __total_deleted=0 469 for i in "${__changes_array[@]}" 470 do 471 if [[ $i =~ $__diff_regex ]] ; then 472 local __added=${BASH_REMATCH[1]} 473 local __deleted=${BASH_REMATCH[2]} 474 __total_added=$(( $__total_added+$__added )) 475 __total_deleted=$(( $__total_deleted+$__deleted )) 476 fi 477 done 478 eval "$2=( $__total_added $__total_deleted )" 479 } 480 481 function get_scope_auto { 482 local __verbose=$2 483 local __total=0 484 local __since=0 485 local __scope= 486 487 get_total_lines __total 488 get_sincetag_lines $finalversion __since 489 490 local __percentage=0 491 if [ "$__total" != "0" ]; then 492 local __percentage=$(( 100*$__since/$__total )) 493 if [ $__percentage -gt "10" ]; then 494 __scope="minor" 495 else 496 __scope="patch" 497 fi 498 fi 499 500 eval "$1=$__scope" 501 if [[ -n "$__verbose" ]]; then 502 echo "[Auto Scope] Percentage of lines changed: $__percentage" 503 echo "[Auto Scope] : $__scope" 504 fi 505 } 506 507 function get_work_tree_status { 508 # Update the index 509 git update-index -q --ignore-submodules --refresh > /dev/null 510 eval "$1=" 511 512 if ! git diff-files --quiet --ignore-submodules -- > /dev/null 513 then 514 eval "$1=unstaged" 515 fi 516 517 if ! git diff-index --cached --quiet HEAD --ignore-submodules -- > /dev/null 518 then 519 eval "$1=uncommitted" 520 fi 521 } 522 523 function get_current { 524 if [ "$hasversiontag" == "true" ]; then 525 local __commitcount="$(git rev-list $lastversion.. --count)" 526 else 527 local __commitcount="$(git rev-list --count HEAD)" 528 fi 529 local __status= 530 get_work_tree_status __status 531 532 if [ "$__commitcount" == "0" ] && [ -z "$__status" ]; then 533 eval "$1=$lastversion" 534 else 535 local __buildinfo="$(git rev-parse --short HEAD)" 536 local __currentbranch="$(git rev-parse --abbrev-ref HEAD)" 537 if [ "$__currentbranch" != "master" ]; then 538 __buildinfo="$__currentbranch.$__buildinfo" 539 fi 540 541 local __suffix= 542 if [ "$__commitcount" != "0" ]; then 543 if [ -n "$__suffix" ]; then 544 __suffix="$__suffix." 545 fi 546 __suffix="$__suffix$__commitcount" 547 fi 548 if [ -n "$__status" ]; then 549 if [ -n "$__suffix" ]; then 550 __suffix="$__suffix." 551 fi 552 __suffix="$__suffix$__status" 553 fi 554 555 __suffix="$__suffix+$__buildinfo" 556 if [ "$lastversion" == "$finalversion" ]; then 557 scope="patch" 558 identifier= 559 local __bumped= 560 bump_version __bumped 561 eval "$1=$__bumped-dev.$__suffix" 562 else 563 eval "$1=$lastversion.$__suffix" 564 fi 565 fi 566 } 567 568 function init { 569 git fetch > /dev/null 570 TAGS="$(git tag)" 571 IFS=$'\n' read -rd '' -a TAG_ARRAY <<<"$TAGS" 572 573 get_latest ${TAG_ARRAY[@]} 574 currentbranch="$(git rev-parse --abbrev-ref HEAD)" 575 } 576 577 case $ACTION in 578 --help) 579 echo -e "$HELP" 580 ;; 581 --version) 582 echo -e "${PROG}: $PROG_VERSION" 583 ;; 584 final) 585 init 586 diff=$(git diff master | cat) 587 diff_dev=$(git diff develop | cat) 588 diff_v2=$(git diff v2 | cat) 589 if [ "$forcetag" == "false" ]; then 590 if [ -n "$diff_dev" ] && [ -n "$diff" ] && [ -n "$diff_v2" ]; then 591 echo "ERROR: Branch must be updated with develop, master, or v2 for final versions" 592 exit 1 593 fi 594 fi 595 increase_version 596 ;; 597 alpha|beta) 598 init 599 identifier="$ACTION" 600 increase_version 601 ;; 602 candidate) 603 init 604 identifier="rc" 605 increase_version 606 ;; 607 getlast) 608 init 609 echo "$lastversion" 610 ;; 611 getfinal) 612 init 613 echo "$finalversion" 614 ;; 615 getcurrent) 616 init 617 get_current current 618 echo "$current" 619 ;; 620 get) 621 init 622 echo "Current final version: $finalversion" 623 echo "Last tagged version: $lastversion" 624 ;; 625 *) 626 echo "'$ACTION' is not a valid command, see --help for available commands." 627 ;; 628 esac