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