[
  {
    "path": ".gitignore",
    "content": "### Vim ###\n# swap\n[._]*.s[a-w][a-z]\n[._]s[a-w][a-z]\n# session\nSession.vim\n# temporary\n.netrwhist\n*~\n# auto-generated tag files\ntags\n# if I open this with Intellij\n.idea\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Semtag\n\nSemantic Tagging Script for Git\n\n[Version: v0.2.0]\n\nNotes: *This script is inspired by the [Nebula Release Plugin](https://github.com/nebula-plugins/nebula-release-plugin), and borrows a couple of lines from [Semver Bash Tool](https://github.com/fsaintjacques/semver-tool) (mostly the version comparison and the semantic version regex).*\n\n[A quick history of this script](https://medium.com/@dr_notsokind/semantic-tagging-with-git-1254dbded22)\n\nThis is a script to help out version bumping on a project following the [Semantic Versioning](http://semver.org/) specification. It uses Git Tags to keep track the versions and the commit log between them, so no extra files are needed. It can be combined with release scripts, git hooks, etc, to have a consistent versioning.\n\n### Why Bash? (and requirements)\n\nPortability, mostly. You can use the script in any project that uses Git as a version control system. The only requirement is Git.\n\n### Why not use the Nebula-release plugin?\n\nNebula Release is for releasing and publishing components and tries to automate the whole process from tagging to publishing. The goal of the `semtag` script is to only tag release versions, leaving the release process up to the developer.\n\nPlus, the `semtag` script doesn't depend on the build system (so no need to use Gradle), so it can be used in any project.\n\n## Usage\n\nCopy the `semtag` script in your project's directory.\n\nSemtag distinguishes between final versions and non-final versions. Possible non-final versions are `alpha`, `beta` and `rc` (release candidate).\n\nStarts from version `0.0.0`, so the first time you initialize a version, it will tag it with the following bumped one (`1.0.0` if major, `0.1.0` if minor, `0.0.1` if patch)\n\nUse the script as follows:\n\n```\nsemtag <command> <options>\n```\n\nInfo commands:\n\n* `getfinal` Returns the current final version.\n* `getlast` Returns the last tagged version, it can be the final version or a non-final version.\n* `getcurrent` Returns the current version, it can be the tagged final version or a tagged non-final version. If there are unstaged or uncommitted changes, they will be included in the version, following this format: `<major>.<minor>.<patch>-dev.#+<branch>.<hash>`. Where `#` is the number of commits since the last final release, `branch` will be the current branch if we are not in the default branch (`master`, `main`, or other) and `hash` is the git hash of the current commit.\n* `get` Returns both last tagged version and current final version.\n\nVersioning commands:\n\n* `final` Bumps the version top a final version\n* `alpha` Bumps the version top an alpha version (appending `-alpha.#` to the version.\n* `beta` Bumps the version top a beta version (appending `-beta.#` to the version.\n* `candidate` Bumps the version top an release candidate version (appending `-rc.#` to the version.\n\nNote: If there are no commits since the last final version, the version is not bumped.\n\nAll versioning commands tags the project with the new version using annotated tags (the tag message contains the list of commits included in the tag), and pushes the tag to the origin remote.\n\nIf you don't want to tag, but just display which would be the next bumped version, use the flag `-o` for showing the output only.\n\nFor specifying the scope you want to bump the version, use the `-s <scope>` option. Possible scopes are `major`, `minor` and `patch`. There is also `auto` which will choose between `minor` and `patch` depending on the percentage of lines changed. Usually it should be the developers decisions which scope to use, since the percentage of lines is not a great criteria, but this option is to help giving a more meaningful versioning when using in automatic scripts.\n\nIf you want to manually set a version, use the `-v <version>` option. Version must comply the semantic versioning specification (`v<major>.<minor>.<patch>`), and must be higher than the latest version. Works with any versioning command.\n\n### Usage Examples\n\nSee the `release` script as an example. The script gets the next version to tag, uses that version to update the `README.md` file (this one!), and the script's. Then commits the changes, and finally tags the project with this latest version.\n\n#### Gradle example\n\nFor setting up your project's version, in your `build.gradle` file, add the following:\n\n```\nversion=getVersionTag()\n\ndef getVersionTag() {\n  def hashStdOut = new ByteArrayOutputStream()\n  exec {\n    commandLine \"$rootProject.projectDir/semtag\", \"getcurrent\"\n    standardOutput = hashStdOut\n  }\n\n  return hashStdOut.toString().trim()\n}\n```\n\nThis way, the project's version every time you make a build, will be aligned with the tagged version. On your CI script, you can tag the release version before deploying, or alternatively, before publishing to a central repository (such as Artifactory), you can create a Gradle task tagging the release version:\n\n```\ndef tagFinalVersion() {\n  exec {\n    commandLine \"$rootProject.projectDir/semtag\", \"final\", \"-s minor\"\n    standardOutput = hashStdOut\n  }\n\n  doLast {\n    project.version=getVersionTag()\n  }\n}\n\nartifactoryPublish.dependsOn tagFinalVersion\n```\n\nOr create your own task for tagging and releasing. The goal of this script is to provide flexibility on how to manage and deal with the releases and deploys.\n\n## How does it bump\n\nSemtag tries to guess which is the following version by using the current final version as a reference for bumping. For example:\n\n```\n$ semtag get\nCurrent final version: v1.0.0\nLast tagged version:   v1.0.0\n$ semtag candidate -s minor\n$ semtag get\nCurrent final version: v1.0.0\nLast tagged version:   v1.1.0-rc.1\n```\n\nAbove it used the `v1.0.0` version for bumping a minor release candidate. If we try to increase a patch:\n\n```\n$ semtag candidate -s patch\n$ semtag get\nCurrent final version: v1.0.0\nLast tagged version:   v1.1.0-rc.2\n```\n\nAgain, it used the `v1.0.0` version as a reference to increase the patch version (so it should be bumped to `v1.0.1-rc.1`), but since the last tagged version is higher, it bumped the release candidate number instead. If we release a beta version:\n\n```\n$ semtag beta -s patch\n$ semtag get\nCurrent final version: v1.0.0\nLast tagged version:   v1.1.1-beta.1\n```\n\nNow the patch has been bumped, since a beta version is considered to be lower than a release candidate, so is the verison number that bumps up, using the provided scope (`patch` in this case).\n\n### Forcing a tag\n\nSemtag doesn't tag if there are no new commits since the last version, or if there are unstaged changes. To force to tag, use the `-f` flag, then it will bump no matter if there are unstaged changes or no new commits.\n\n### Version prefix\n\nBy default, semtag prefixes new versions with `v`. Use the `-p` (plain) flag to create new versions with no `v` prefix.\n\n### Tag prefix\n\nFor projects managing multiple components in a single repository, you can use custom tag prefixes to organize versions independently. Use the `-P <prefix>` flag to add a custom prefix to your tags.\n\n```bash\n# Default behavior\nsemtag alpha -s patch               # Creates: v0.0.1-alpha.1\n\n# With custom prefix\nsemtag alpha -s patch -P service    # Creates: service-v0.0.1-alpha.1\n\n# Combine with -p flag for plain versions (no 'v')\nsemtag alpha -s patch -P api -p     # Creates: api-0.0.1-alpha.1\n```\n\nEach tag prefix maintains its own independent version sequence, allowing you to version multiple components separately:\n\n```bash\n# Version the backend service\nsemtag final -s minor -P backend    # Creates: backend-v0.1.0\n\n# Version the frontend independently\nsemtag final -s patch -P frontend   # Creates: frontend-v0.0.1\n\n# Default tags remain separate\nsemtag final -s major               # Creates: v1.0.0\n```\n\nQuery versions by prefix:\n\n```bash\nsemtag getlast -P backend           # Returns: backend-v0.1.0\nsemtag getfinal -P frontend         # Returns: frontend-v0.0.1\nsemtag getlast                      # Returns: v1.0.0\n```\n\nLicense\n=======\n\n    Copyright 2020 Nico Hormazábal\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n"
  },
  {
    "path": "release.sh",
    "content": "#!/usr/bin/env bash\n\n# release.sh - Release automation script (Refactored)\n# Improved version with better error handling, validation, and maintainability\n\nset -euo pipefail  # Fail fast on errors, undefined variables, and pipe failures\n\nreadonly SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nreadonly SEMTAG_SCRIPT=\"${SCRIPT_DIR}/semtag\"\nreadonly README_FILE=\"${SCRIPT_DIR}/README.md\"\n\n# Configuration with defaults\ndeclare -A CONFIG=(\n    [scope]=\"${1:-auto}\"\n    [dry_run]=\"false\"\n    [verbose]=\"false\"\n)\n\n# Utility functions\nlog() {\n    echo \"[$(date +'%Y-%m-%d %H:%M:%S')] $*\" >&2\n}\n\nerror() {\n    log \"ERROR: $*\"\n    exit 1\n}\n\nverbose() {\n    [[ \"${CONFIG[verbose]}\" == \"true\" ]] && log \"$*\"\n}\n\n# Validate dependencies\ncheck_dependencies() {\n    if [[ ! -f \"$SEMTAG_SCRIPT\" ]]; then\n        error \"semtag script not found at: $SEMTAG_SCRIPT\"\n    fi\n\n    if [[ ! -x \"$SEMTAG_SCRIPT\" ]]; then\n        log \"Making semtag executable...\"\n        chmod +x \"$SEMTAG_SCRIPT\"\n    fi\n\n    if [[ ! -f \"$README_FILE\" ]]; then\n        error \"README.md not found at: $README_FILE\"\n    fi\n\n    # Check if we're in a git repository\n    if ! git rev-parse --git-dir >/dev/null 2>&1; then\n        error \"Not in a git repository\"\n    fi\n\n    # Check for uncommitted changes (unless dry run)\n    if [[ \"${CONFIG[dry_run]}\" == \"false\" ]]; then\n        if ! git diff --quiet HEAD 2>/dev/null; then\n            error \"Repository has uncommitted changes. Commit or stash them first.\"\n        fi\n    fi\n}\n\n# Validate scope parameter\nvalidate_scope() {\n    local scope=\"${CONFIG[scope]}\"\n    case \"$scope\" in\n        auto|major|minor|patch)\n            verbose \"Using scope: $scope\"\n            ;;\n        *)\n            error \"Invalid scope '$scope'. Must be one of: auto, major, minor, patch\"\n            ;;\n    esac\n}\n\n# Get next version without tagging\nget_next_version() {\n    local version\n    verbose \"Getting next version with scope '${CONFIG[scope]}'...\"\n\n    # Use the semtag script to get next version (dry run mode)\n    if ! version=$(\"$SEMTAG_SCRIPT\" final -o -s \"${CONFIG[scope]}\" 2>/dev/null); then\n        error \"Failed to get next version from semtag\"\n    fi\n\n    if [[ -z \"$version\" ]]; then\n        error \"semtag returned empty version\"\n    fi\n\n    verbose \"Next version will be: $version\"\n    echo \"$version\"\n}\n\n# Update version in files\nupdate_version_in_files() {\n    local version=\"$1\"\n\n    log \"Updating version to $version in source files...\"\n\n    # Create backup function for safer file operations\n    backup_file() {\n        local file=\"$1\"\n        if [[ -f \"$file\" ]]; then\n            cp \"$file\" \"${file}.backup\"\n            verbose \"Created backup: ${file}.backup\"\n        fi\n    }\n\n    # Restore backup function\n    restore_backup() {\n        local file=\"$1\"\n        if [[ -f \"${file}.backup\" ]]; then\n            mv \"${file}.backup\" \"$file\"\n            log \"Restored backup for $file\"\n        fi\n    }\n\n    # Clean backup function\n    cleanup_backup() {\n        local file=\"$1\"\n        if [[ -f \"${file}.backup\" ]]; then\n            rm \"${file}.backup\"\n            verbose \"Cleaned up backup: ${file}.backup\"\n        fi\n    }\n\n    # Trap to cleanup backups on error\n    trap 'restore_backup \"$SEMTAG_SCRIPT\"; restore_backup \"$README_FILE\"' ERR\n\n    # Update semtag script version\n    backup_file \"$SEMTAG_SCRIPT\"\n\n    if ! sed -i '' \"s/^PROG_VERSION=\\\"[^\\\"]*\\\"/PROG_VERSION=\\\"$version\\\"/g\" \"$SEMTAG_SCRIPT\"; then\n        error \"Failed to update version in $SEMTAG_SCRIPT\"\n    fi\n    verbose \"Updated version in semtag script\"\n\n    # Update README.md version\n    backup_file \"$README_FILE\"\n\n    if ! sed -i '' \"s/^\\[Version: [^]]*\\]/[Version: $version]/g\" \"$README_FILE\"; then\n        error \"Failed to update version in $README_FILE\"\n    fi\n    verbose \"Updated version in README.md\"\n\n    # Verify changes were made\n    if ! grep -q \"PROG_VERSION=\\\"$version\\\"\" \"$SEMTAG_SCRIPT\"; then\n        error \"Version update verification failed for $SEMTAG_SCRIPT\"\n    fi\n\n    if ! grep -q \"\\\\[Version: $version\\\\]\" \"$README_FILE\"; then\n        error \"Version update verification failed for $README_FILE\"\n    fi\n\n    # Clean up backups on success\n    cleanup_backup \"$SEMTAG_SCRIPT\"\n    cleanup_backup \"$README_FILE\"\n\n    # Disable trap\n    trap - ERR\n\n    log \"Successfully updated version in all files\"\n}\n\n# Commit and push changes\ncommit_and_push_changes() {\n    local version=\"$1\"\n\n    log \"Committing changes for version $version...\"\n\n    # Stage the modified files\n    if ! git add \"$SEMTAG_SCRIPT\" \"$README_FILE\"; then\n        error \"Failed to stage modified files\"\n    fi\n\n    # Commit with descriptive message\n    local commit_message=\"Update version info to $version\n\n- Updated PROG_VERSION in semtag script\n- Updated version badge in README.md\n\nAutomated release preparation commit.\"\n\n    if ! git commit -m \"$commit_message\"; then\n        error \"Failed to commit changes\"\n    fi\n\n    verbose \"Changes committed successfully\"\n\n    # Push changes\n    log \"Pushing changes to remote...\"\n    if ! git push; then\n        error \"Failed to push changes to remote\"\n    fi\n\n    log \"Changes pushed successfully\"\n}\n\n# Create final tag\ncreate_final_tag() {\n    local version=\"$1\"\n\n    log \"Creating final tag for version $version...\"\n\n    # Use semtag to create the final tag\n    if ! \"$SEMTAG_SCRIPT\" final -f -v \"$version\"; then\n        error \"Failed to create final tag with semtag\"\n    fi\n\n    log \"Successfully created and pushed tag: $version\"\n}\n\n# Show release summary\nshow_summary() {\n    local version=\"$1\"\n\n    log \"Release Summary:\"\n    log \"  Version: $version\"\n    log \"  Scope: ${CONFIG[scope]}\"\n    log \"  Files updated: semtag, README.md\"\n    log \"  Git tag created and pushed: $version\"\n    log \"\"\n    log \"Release completed successfully!\"\n}\n\n# Dry run mode\nperform_dry_run() {\n    local version\n    version=$(get_next_version)\n\n    log \"DRY RUN MODE - No changes will be made\"\n    log \"Would release version: $version\"\n    log \"Would update files: $SEMTAG_SCRIPT, $README_FILE\"\n    log \"Would commit and push changes\"\n    log \"Would create and push git tag: $version\"\n}\n\n# Parse command line arguments\nparse_arguments() {\n    while [[ $# -gt 0 ]]; do\n        case $1 in\n            --dry-run)\n                CONFIG[dry_run]=\"true\"\n                shift\n                ;;\n            --verbose|-v)\n                CONFIG[verbose]=\"true\"\n                shift\n                ;;\n            --help|-h)\n                show_help\n                exit 0\n                ;;\n            -*)\n                error \"Unknown option: $1\"\n                ;;\n            *)\n                # Assume it's a scope parameter\n                CONFIG[scope]=\"$1\"\n                shift\n                ;;\n        esac\n    done\n}\n\n# Show help\nshow_help() {\n    cat << EOF\nUsage: $(basename \"$0\") [SCOPE] [OPTIONS]\n\nRelease automation script for semtag.\n\nArguments:\n  SCOPE           Version scope to bump (auto|major|minor|patch) [default: auto]\n\nOptions:\n  --dry-run       Show what would be done without making changes\n  --verbose, -v   Enable verbose output\n  --help, -h      Show this help message\n\nExamples:\n  $(basename \"$0\")                    # Auto-detect scope and release\n  $(basename \"$0\") minor             # Release with minor version bump\n  $(basename \"$0\") --dry-run         # Preview release actions\n  $(basename \"$0\") patch --verbose   # Release patch with verbose output\n\nThis script will:\n1. Get the next version using semtag\n2. Update version strings in semtag and README.md\n3. Commit and push the changes\n4. Create and push a git tag using semtag\nEOF\n}\n\n# Main execution function\nmain() {\n    # Parse arguments (excluding the scope which is handled in CONFIG initialization)\n    shift 2>/dev/null || true  # Remove scope from $@ if present\n    parse_arguments \"$@\"\n\n    log \"Starting release process with scope: ${CONFIG[scope]}\"\n\n    # Validate everything first\n    check_dependencies\n    validate_scope\n\n    # Handle dry run\n    if [[ \"${CONFIG[dry_run]}\" == \"true\" ]]; then\n        perform_dry_run\n        return 0\n    fi\n\n    # Execute release process\n    local version\n    version=$(get_next_version)\n\n    update_version_in_files \"$version\"\n    commit_and_push_changes \"$version\"\n    create_final_tag \"$version\"\n    show_summary \"$version\"\n}\n\n# Only run main if script is executed directly (not sourced)\nif [[ \"${BASH_SOURCE[0]}\" == \"${0}\" ]]; then\n    main \"$@\"\nfi"
  },
  {
    "path": "semtag",
    "content": "#!/usr/bin/env bash\n\n# Enable strict error handling\nset -euo pipefail\n\nPROG=semtag\nPROG_VERSION=\"v0.1.2\"\n\n# Error handling function\nerror_exit() {\n    local error_message=\"$1\"\n    local exit_code=\"${2:-1}\"\n    echo \"ERROR: $error_message\" >&2\n    exit \"$exit_code\"\n}\n\n# Check if we're in a git repository\ncheck_git_repo() {\n    if ! git rev-parse --git-dir >/dev/null 2>&1; then\n        error_exit \"Not in a git repository. Please run this script from within a git repository.\"\n    fi\n}\n\nSEMVER_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-]+)*)?$\"\nIDENTIFIER_REGEX=\"^\\-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*$\"\nNUMERIC_REGEX='^[0-9]+$'\n\n# Length limit for the branch name in build metadata.\nMAX_BRANCH_LENGTH=50\n\n# Global variables\nFIRST_VERSION=\"v0.0.0\"\nfinalversion=$FIRST_VERSION\nlastversion=$FIRST_VERSION\nhasversiontag=\"false\"\nscope=\"patch\"\ndisplayonly=\"false\"\nforcetag=\"false\"\nprefix=\"v\"\ntag_prefix=\"\"\nforcedversion=\nversionname=\nidentifier=\n\nHELP=\"\\\nUsage:\n  $PROG\n  $PROG getlast\n  $PROG getfinal\n  $PROG (final|alpha|beta|candidate) [-s <scope> (major|minor|patch|auto) | -o]\n  $PROG --help\n  $PROG --version\nOptions:\n  -s         The scope that must be increased, can be major, minor or patch.\n               The resulting version will match X.Y.Z(-PRERELEASE)(+BUILD)\n               where X, Y and Z are positive integers, PRERELEASE is an optionnal\n               string composed of alphanumeric characters describing if the build is\n               a release candidate, alpha or beta version, with a number.\n               BUILD is also an optional string composed of alphanumeric\n               characters and hyphens.\n               Setting the scope as 'auto', the script will chose the scope between\n               'minor' and 'patch', depending on the amount of lines added (<10% will\n               choose patch).\n  -v         Specifies manually the version to be tagged, must be a valid semantic version\n               in the format X.Y.Z where X, Y and Z are positive integers.\n  -o         Output the version only, shows the bumped version, but doesn't tag.\n  -f         Forces to tag, even if there are unstaged or uncommited changes.\n  -p         Use a plain version, ie. do not prefix with 'v'.\n  -P         Add a custom prefix to the tag (e.g., -P service generates service-v0.0.1).\nCommands:\n  --help     Print this help message.\n  --version  Prints the program's version.\n  get        Returns both current final version and last tagged version.\n  getlast    Returns the latest tagged version.\n  getfinal   Returns the latest tagged final version.\n  getcurrent Returns the current version, based on the latest one, if there are uncommited or\n               unstaged changes, they will be reflected in the version, adding the number of\n               pending commits, current branch and commit hash.\n  final      Tags the current build as a final version, this only can be done on the default branch.\n  candidate  Tags the current build as a release candidate, the tag will contain all\n               the commits from the last final version.\n  alpha      Tags the current build as an alpha version, the tag will contain all\n               the commits from the last final version.\n  beta       Tags the current build as a beta version, the tag will contain all\n               the commits from the last final version.\"\n\n# Validate input arguments\nvalidate_scope() {\n    local scope=\"$1\"\n    case \"$scope\" in\n        major|minor|patch|auto) ;;\n        *) error_exit \"Invalid scope '$scope'. Valid scopes are: major, minor, patch, auto\" ;;\n    esac\n}\n\nvalidate_version() {\n    local version=\"$1\"\n    if [[ ! \"$version\" =~ $SEMVER_REGEX ]]; then\n        error_exit \"Invalid version format '$version'. Must follow semantic versioning (e.g., 1.2.3)\"\n    fi\n}\n\n# Commands and options\nACTION=\"${1:-getlast}\"\nif [[ $# -gt 0 ]]; then\n    shift\nfi\n\n# We get the parameters\nwhile getopts \"v:s:ofpP:\" opt; do\n  case $opt in\n    v)\n      forcedversion=\"$OPTARG\"\n      validate_version \"$forcedversion\"\n      ;;\n    s)\n      scope=\"$OPTARG\"\n      validate_scope \"$scope\"\n      ;;\n    o)\n      displayonly=\"true\"\n      ;;\n    f)\n      forcetag=\"true\"\n      ;;\n    p)\n      prefix=\"\"\n      ;;\n    P)\n      tag_prefix=\"$OPTARG-\"\n      ;;\n    \\?)\n      error_exit \"Invalid option: -$OPTARG\"\n      ;;\n    :)\n      error_exit \"Option -$OPTARG requires an argument.\"\n      ;;\n  esac\ndone\n\n# Try to programmatically fetch the default branch. Go by the first remote HEAD found, otherwise default to `master`.\n# $1 The variable to store the result\nfunction get_default_branch {\n  local __result=\"$1\"\n  local __default_branch=\"\"\n\n  local __remotes\n  if ! __remotes=$(git remote 2>/dev/null); then\n    error_exit \"Failed to get git remotes\"\n  fi\n\n  if [[ -n \"$__remotes\" ]]; then\n    while IFS= read -r __remote; do\n      local __default_branch_ref\n      if __default_branch_ref=$(git symbolic-ref --quiet \"refs/remotes/${__remote}/HEAD\" 2>/dev/null); then\n        __default_branch=\"${__default_branch_ref#refs/remotes/${__remote}/}\"\n        if [[ -n \"$__default_branch\" ]]; then\n          break\n        fi\n      fi\n    done <<< \"$__remotes\"\n  fi\n\n  printf -v \"$__result\" '%s' \"${__default_branch:-master}\"\n}\n\n# Gets a string with the version and returns an array of maximum size of 5 with all the parts of the sematinc version\n# $1 The string containing the version in semantic format\n# $2 The variable to store the result array:\n#      position 0: major number\n#      position 1: minor number\n#      position 2: patch number\n#      position 3: identifier (or prerelease identifier)\n#      position 4: build info\nfunction explode_version {\n  local __version=\"$1\"\n  local __result=\"$2\"\n\n  if [[ \"$__version\" =~ $SEMVER_REGEX ]] ; then\n    local __major=\"${BASH_REMATCH[1]}\"\n    local __minor=\"${BASH_REMATCH[2]}\"\n    local __patch=\"${BASH_REMATCH[3]}\"\n    local __prere=\"${BASH_REMATCH[4]}\"\n    local __build=\"${BASH_REMATCH[5]}\"\n    # Use eval for compatibility with older bash versions\n    eval \"$__result=(\\\"\\$__major\\\" \\\"\\$__minor\\\" \\\"\\$__patch\\\" \\\"\\$__prere\\\" \\\"\\$__build\\\")\"\n  else\n    # Clear the array\n    eval \"$__result=()\"\n  fi\n}\n\n# Compare two versions and returns -1, 0 or 1\n# $1 The first version to compare\n# $2 The second version to compare\n# $3 The variable where to store the result\nfunction compare_versions {\n  local __first __second\n  explode_version \"$1\" __first\n  explode_version \"$2\" __second\n  local lv=\"$3\"\n\n  # Compares MAJOR, MINOR and PATCH\n  for i in 0 1 2; do\n    local __numberfirst=\"${__first[$i]:-0}\"\n    local __numbersecond=\"${__second[$i]:-0}\"\n    local __diff=$(((__numberfirst) - (__numbersecond)))\n    case \"$__diff\" in\n      0)\n        ;;\n      -*)\n        printf -v \"$lv\" '%s' \"-1\"\n        return 0\n        ;;\n      *)\n        printf -v \"$lv\" '%s' \"1\"\n        return 0\n        ;;\n    esac\n  done\n\n  # Identifiers should compare with the ASCII order.\n  local compareresult\n  compare_identifiers \"${__first[3]:-}\" \"${__second[3]:-}\" compareresult\n  printf -v \"$lv\" '%s' \"$compareresult\"\n}\n\n\n# Returns the number comparison\n# $1 The first number to compare\n# $2 The second number to compare\n# $3 The variable where to store the result\nfunction compare_numeric {\n  local __first=\"$1\"\n  local __second=\"$2\"\n  local __result=\"$3\"\n\n  if (( __first < __second )) ; then\n    printf -v \"$__result\" '%s' \"-1\"\n  elif (( __first > __second )) ; then\n    printf -v \"$__result\" '%s' \"1\"\n  else\n    printf -v \"$__result\" '%s' \"0\"\n  fi\n}\n\n# Returns the alpanumeric comparison\n# $1 The first alpanumeric to compare\n# $2 The second alpanumeric to compare\n# $3 The variable where to store the result\nfunction compare_alphanumeric {\n  local __first=\"$1\"\n  local __second=\"$2\"\n  local __result=\"$3\"\n\n  if [[ \"$__first\" < \"$__second\" ]] ; then\n    printf -v \"$__result\" '%s' \"-1\"\n  elif [[ \"$__first\" > \"$__second\" ]] ; then\n    printf -v \"$__result\" '%s' \"1\"\n  else\n    printf -v \"$__result\" '%s' \"0\"\n  fi\n}\n\n# Returns the last version of two\n# $1 The first version to compare\n# $2 The second version to compare\n# $3 The variable where to store the last one\nfunction get_latest_of_two {\n  local __first=\"$1\"\n  local __second=\"$2\"\n  local __result\n  local __latest=\"$3\"\n\n  compare_versions \"$__first\" \"$__second\" __result\n  case \"$__result\" in\n    0)\n      printf -v \"$__latest\" '%s' \"$__second\"\n      ;;\n    -1)\n      printf -v \"$__latest\" '%s' \"$__second\"\n      ;;\n    1)\n      printf -v \"$__latest\" '%s' \"$__first\"\n      ;;\n  esac\n}\n\n# Returns comparison of two identifier parts\n# $1 The first part to compare\n# $2 The second part to compare\n# $3 The variable where to store the compare result\nfunction compare_identifier_part {\n  local __first=$1\n  local __second=$2\n  local __result=$3\n  local compareresult\n\n  if [[ \"$__first\" =~ $NUMERIC_REGEX ]] && [[ \"$__second\" =~ $NUMERIC_REGEX ]] ; then\n    compare_numeric \"$__first\" \"$__second\" compareresult\n    eval \"$__result=$compareresult\"\n    return 0\n  fi\n\n\n  compare_alphanumeric \"$__first\" \"$__second\" compareresult\n  eval \"$__result=$compareresult\"\n}\n\n# Returns comparison of two identifiers\n# $1 The first identifier to compare\n# $2 The second identifier to compare\n# $3 The variable where to store the compare result\nfunction compare_identifiers {\n  local __first=$1\n  local __second=$2\n  local __result=$3\n  local partresult\n  local arraylengths\n  if [[ -n \"$__first\" ]] && [[ -n \"$__second\" ]]; then\n    explode_identifier \"${__first}\" explodedidentifierfirst\n    explode_identifier \"${__second}\" explodedidentifiersecond\n\n    firstsize=${#explodedidentifierfirst[@]}\n    secondsize=${#explodedidentifiersecond[@]}\n    minlength=$(( $firstsize<$secondsize ? $firstsize : $secondsize ))\n    for (( i = 0 ; i < $minlength ; i++ )); do\n      compare_identifier_part \"${explodedidentifierfirst[$i]}\" \"${explodedidentifiersecond[$i]}\" partresult\n      case $partresult in\n        0)\n          ;;\n        *)\n          eval \"$__result=$partresult\"\n          return 0\n          ;;\n      esac\n    done\n    compare_numeric $firstsize $secondsize arraylengths\n    eval \"$__result=$arraylengths\"\n    return 0\n  elif [[ -z \"$__first\" ]] && [[ -n \"$__second\" ]]; then\n    eval \"$__result=1\"\n    return 0\n  elif [[ -n \"$__first\" ]] && [[ -z \"$__second\" ]]; then\n    eval \"$__result=-1\"\n    return 0\n  fi\n\n  eval \"$__result=0\"\n}\n\n# Assigns a 2 size array with the identifier, having the identifier at pos 0, and the number in pos 1\n# $1 The identifier in the format -id.#\n# $2 The vferiable where to store the 2 size array\nfunction explode_identifier {\n  local __identifier=\"$1\"\n  local __result=\"$2\"\n  if [[ \"$__identifier\" =~ $IDENTIFIER_REGEX ]] ; then\n    local identifierparts\n    IFS='-.' read -ra identifierparts <<< \"$__identifier\"\n    # Filter out empty elements (the first element is empty when splitting \"-beta.1\")\n    local filtered=()\n    for part in \"${identifierparts[@]}\"; do\n      if [[ -n \"$part\" ]]; then\n        filtered+=(\"$part\")\n      fi\n    done\n    eval \"$__result=( \\\"\\${filtered[@]}\\\" )\"\n  else\n    eval \"$__result=()\"\n  fi\n}\n\n# Gets a list of tags and assigns the base and latest versions\n# Receives an array with the tags containing the versions\n# Assigns to the global variables finalversion and lastversion the final version and the latest version\nfunction get_latest {\n  local __taglist=(\"$@\")\n  local __tagsnumber=${#__taglist[@]}\n  local __current\n  case $__tagsnumber in\n    0)\n      finalversion=\"$FIRST_VERSION\"\n      lastversion=\"$FIRST_VERSION\"\n      ;;\n    1)\n      __current=\"${__taglist[0]}\"\n      local ver\n      explode_version \"$__current\" ver\n      if [ ${#ver[@]} -gt 0 ]; then\n        if [ -n \"${ver[3]:-}\" ]; then\n          finalversion=\"$FIRST_VERSION\"\n        else\n          finalversion=\"$__current\"\n        fi\n        lastversion=\"$__current\"\n      else\n        finalversion=\"$FIRST_VERSION\"\n        lastversion=\"$FIRST_VERSION\"\n      fi\n      ;;\n    *)\n      local __lastpos=$((__tagsnumber-1))\n      for i in $(seq 0 \"$__lastpos\")\n      do\n        __current=\"${__taglist[i]}\"\n        local ver\n        explode_version \"$__current\" ver\n        if [ ${#ver[@]} -gt 0 ]; then\n          if [ -z \"${ver[3]:-}\" ]; then\n            get_latest_of_two \"$finalversion\" \"$__current\" finalversion\n            get_latest_of_two \"$lastversion\" \"$finalversion\" lastversion\n          else\n            get_latest_of_two \"$lastversion\" \"$__current\" lastversion\n          fi\n        fi\n      done\n      ;;\n  esac\n\n  if git rev-parse -q --verify \"refs/tags/$tag_prefix$lastversion\" >/dev/null 2>&1; then\n    hasversiontag=\"true\"\n  else\n    hasversiontag=\"false\"\n  fi\n}\n\n# Gets the next version given the provided scope\n# $1 The version that is going to be bumped\n# $2 The scope to bump\n# $3 The variable where to stoer the result\nfunction get_next_version {\n  local __exploded\n  local __fromversion=\"$1\"\n  local __scope=\"$2\"\n  local __result=\"$3\"\n  explode_version \"$__fromversion\" __exploded\n  case \"$__scope\" in\n    major)\n      __exploded[0]=$((${__exploded[0]}+1))\n      __exploded[1]=0\n      __exploded[2]=0\n    ;;\n    minor)\n      __exploded[1]=$((${__exploded[1]}+1))\n      __exploded[2]=0\n    ;;\n    patch)\n      __exploded[2]=$((${__exploded[2]}+1))\n    ;;\n  esac\n\n  printf -v \"$__result\" '%s' \"${prefix}${__exploded[0]}.${__exploded[1]}.${__exploded[2]}\"\n}\n\nfunction bump_version {\n  ## First we try to get the next version based on the existing last one\n  if [ \"$scope\" == \"auto\" ]; then\n    get_scope_auto scope\n  fi\n\n  local __candidatefromlast=$FIRST_VERSION\n  local __explodedlast\n  explode_version $lastversion __explodedlast\n  if [[ -n \"${__explodedlast[3]}\" ]]; then\n    # Last version is not final\n    local __idlast\n    explode_identifier ${__explodedlast[3]} __idlast\n\n    # We get the last, given the desired id based on the scope\n    __candidatefromlast=\"${prefix}${__explodedlast[0]}.${__explodedlast[1]}.${__explodedlast[2]}\"\n    if [[ -n \"$identifier\" ]]; then\n      local __nextid=\"$identifier.1\"\n      if [ \"$identifier\" == \"${__idlast[0]}\" ]; then\n        # We target the same identifier as the last so we increase one\n        __nextid=\"$identifier.$(( ${__idlast[1]}+1 ))\"\n        __candidatefromlast=\"$__candidatefromlast-$__nextid\"\n      else\n        # Different identifiers, we make sure we are assigning a higher identifier, if not, we increase the version\n        __candidatefromlast=\"$__candidatefromlast-$__nextid\"\n        local __comparedwithlast\n        compare_versions $__candidatefromlast $lastversion __comparedwithlast\n        if [ \"$__comparedwithlast\" == -1 ]; then\n          get_next_version $__candidatefromlast $scope __candidatefromlast\n          __candidatefromlast=\"$__candidatefromlast-$__nextid\"\n        fi\n      fi\n    fi\n  fi\n\n  # Then we try to get the version based on the latest final one\n  local __candidatefromfinal\n  get_next_version $finalversion $scope __candidatefromfinal\n  if [[ -n \"$identifier\" ]]; then\n    __candidatefromfinal=\"$__candidatefromfinal-$identifier.1\"\n  fi\n\n  # Finally we compare both candidates\n  local __resultversion\n  local __result\n  compare_versions $__candidatefromlast $__candidatefromfinal __result\n  case $__result in\n    0)\n      __resultversion=$__candidatefromlast\n      ;;\n    -1)\n      __resultversion=\"$__candidatefromfinal\"\n      ;;\n    1)\n      __resultversion=$__candidatefromlast\n      ;;\n  esac\n\n  printf -v \"$1\" '%s' \"$__resultversion\"\n}\n\nfunction increase_version {\n  local __version=\"\"\n\n  if [ -z \"${forcedversion:-}\" ]; then\n    bump_version __version\n  else\n    if [[ \"$forcedversion\" =~ $SEMVER_REGEX ]] ; then\n      local __result\n      compare_versions \"$forcedversion\" \"$lastversion\" __result\n      if [ \"$__result\" -le 0 ]; then\n        error_exit \"Version '$forcedversion' can't be lower than or equal to last version: $lastversion\"\n      fi\n    else\n      error_exit \"Invalid version format: $forcedversion\"\n    fi\n    __version=\"$forcedversion\"\n  fi\n\n  # Add the tag_prefix to the final version for output or tagging\n  __version=\"${tag_prefix}${__version}\"\n\n  if [ \"$displayonly\" == \"true\" ]; then\n    echo \"$__version\"\n  else\n    if [ \"$forcetag\" == \"false\" ]; then\n      check_git_dirty_status\n    fi\n    local __commitlist\n    if [ \"$finalversion\" == \"$FIRST_VERSION\" ] || [ \"$hasversiontag\" != \"true\" ]; then\n      if ! __commitlist=$(git log --pretty=oneline 2>/dev/null); then\n        error_exit \"Failed to get git commit log\"\n      fi\n    else\n      if ! __commitlist=$(git log --pretty=oneline \"$tag_prefix$finalversion\"... 2>/dev/null); then\n        error_exit \"Failed to get git commit log from $tag_prefix$finalversion\"\n      fi\n    fi\n\n    # If we are forcing a bump, we add bump to the commit list\n    if [[ -z \"$__commitlist\" && \"$forcetag\" == \"true\" ]]; then\n      __commitlist=\"bump\"\n    fi\n\n    if [[ -z \"$__commitlist\" ]]; then\n      echo \"No commits since the last final version, not bumping version\"\n    else\n      if [[ -z \"${versionname:-}\" ]]; then\n        if ! versionname=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\" 2>/dev/null); then\n          error_exit \"Failed to generate timestamp for version name\"\n        fi\n      fi\n      local __message=\"$versionname\n$__commitlist\"\n\n      # We check we have info on the user\n      local __username\n      if ! __username=$(git config user.name 2>/dev/null); then\n        if ! __username=$(id -u -n 2>/dev/null); then\n          error_exit \"Failed to get username\"\n        fi\n        if ! git config user.name \"$__username\" 2>/dev/null; then\n          error_exit \"Failed to set git user.name\"\n        fi\n      fi\n      local __useremail\n      if ! __useremail=$(git config user.email 2>/dev/null); then\n        if ! __useremail=$(hostname 2>/dev/null); then\n          error_exit \"Failed to get hostname for email\"\n        fi\n        if ! git config user.email \"$__username@$__useremail\" 2>/dev/null; then\n          error_exit \"Failed to set git user.email\"\n        fi\n      fi\n\n      if ! git tag -a \"$__version\" -m \"$__message\" 2>/dev/null; then\n        error_exit \"Failed to create git tag: $__version\"\n      fi\n\n      # If we have a remote, we push there\n      local __remotes\n      if __remotes=$(git remote 2>/dev/null) && [[ -n \"$__remotes\" ]]; then\n        while IFS= read -r __remote; do\n          if git push \"$__remote\" \"$__version\" >/dev/null 2>&1; then\n            echo \"$__version pushed to $__remote\"\n          else\n            error_exit \"Failed to push tag $__version to remote $__remote\"\n          fi\n        done <<< \"$__remotes\"\n      else\n        echo \"$__version\"\n      fi\n    fi\n  fi\n}\n\nfunction check_git_dirty_status {\n  local __repostatus=\"\"\n  get_work_tree_status __repostatus\n\n  if [ \"$__repostatus\" == \"uncommitted\" ]; then\n    echo \"ERROR: You have uncommitted changes\" >&2\n    if git status --porcelain 2>/dev/null; then\n      :  # status displayed successfully\n    else\n      echo \"Unable to display git status\" >&2\n    fi\n    exit 1\n  fi\n\n  if [ \"$__repostatus\" == \"unstaged\" ]; then\n    echo \"ERROR: You have unstaged changes\" >&2\n    if git status --porcelain 2>/dev/null; then\n      :  # status displayed successfully\n    else\n      echo \"Unable to display git status\" >&2\n    fi\n    exit 1\n  fi\n}\n\n# Get the total amount of lines of code in the repo\nfunction get_total_lines {\n  local __empty_id\n  if ! __empty_id=$(git hash-object -t tree /dev/null 2>/dev/null); then\n    error_exit \"Failed to get empty tree hash\"\n  fi\n  local __changes\n  if ! __changes=$(git diff --numstat \"$__empty_id\" 2>/dev/null); then\n    error_exit \"Failed to get git diff numstat\"\n  fi\n  local __added_deleted=\"$1\"\n  get_changed_lines \"$__changes\" \"$__added_deleted\"\n}\n\n# Get the total amount of lines of code since the provided tag\nfunction get_sincetag_lines {\n  local __sincetag=\"$1\"\n  local __changes\n  if ! __changes=$(git diff --numstat \"$__sincetag\" 2>/dev/null); then\n    error_exit \"Failed to get git diff numstat since $__sincetag\"\n  fi\n  local __added_deleted=\"$2\"\n  get_changed_lines \"$__changes\" \"$__added_deleted\"\n}\n\nfunction get_changed_lines {\n  local __changes_numstat=\"$1\"\n  local __result=\"$2\"\n  local __changes_array\n  if [[ -n \"$__changes_numstat\" ]]; then\n    IFS=$'\\n' read -rd '' -a __changes_array <<<\"$__changes_numstat\" || true\n  else\n    __changes_array=()\n  fi\n  local __diff_regex=\"^([0-9]+)[[:space:]]+([0-9]+)[[:space:]]+.+$\"\n\n  local __total_added=0\n  local __total_deleted=0\n  for i in \"${__changes_array[@]}\"\n  do\n    if [[ \"$i\" =~ $__diff_regex ]] ; then\n      local __added=\"${BASH_REMATCH[1]}\"\n      local __deleted=\"${BASH_REMATCH[2]}\"\n      __total_added=$(( __total_added + __added ))\n      __total_deleted=$(( __total_deleted + __deleted ))\n    fi\n  done\n  eval \"$__result=( $__total_added $__total_deleted )\"\n}\n\nfunction get_scope_auto {\n  local __result=\"$1\"\n  local __verbose=\"${2:-}\"\n  local __total=()\n  local __since=()\n  local __scope=\"\"\n\n  get_total_lines __total\n  get_sincetag_lines \"$tag_prefix$finalversion\" __since\n\n  local __percentage=0\n  if [ \"${__total[0]}\" != \"0\" ]; then\n    __percentage=$(( 100 * ${__since[0]} / ${__total[0]} ))\n    if [ \"$__percentage\" -gt \"10\" ]; then\n      __scope=\"minor\"\n    else\n      __scope=\"patch\"\n    fi\n  else\n    __scope=\"patch\"\n  fi\n\n  printf -v \"$__result\" '%s' \"$__scope\"\n  if [[ -n \"$__verbose\" ]]; then\n    echo \"[Auto Scope] Percentage of lines changed: $__percentage\"\n    echo \"[Auto Scope] : $__scope\"\n  fi\n}\n\nfunction get_work_tree_status {\n  local __result=\"$1\"\n  # Update the index\n  if ! git update-index -q --ignore-submodules --refresh >/dev/null 2>&1; then\n    error_exit \"Failed to update git index\"\n  fi\n\n  printf -v \"$__result\" '%s' \"\"\n\n  if ! git diff-files --quiet --ignore-submodules -- >/dev/null 2>&1; then\n    printf -v \"$__result\" '%s' \"unstaged\"\n  fi\n\n  if ! git diff-index --cached --quiet HEAD --ignore-submodules -- >/dev/null 2>&1; then\n    printf -v \"$__result\" '%s' \"uncommitted\"\n  fi\n}\n\nfunction get_current {\n  local __result=\"$1\"\n  local __commitcount\n\n  if [ \"$hasversiontag\" == \"true\" ]; then\n    if ! __commitcount=$(git rev-list \"$tag_prefix$lastversion\".. --count 2>/dev/null); then\n      error_exit \"Failed to get commit count from $tag_prefix$lastversion\"\n    fi\n  else\n    if ! __commitcount=$(git rev-list --count HEAD 2>/dev/null); then\n      error_exit \"Failed to get total commit count\"\n    fi\n  fi\n\n  local __status=\"\"\n  get_work_tree_status __status\n\n  if [ \"$__commitcount\" == \"0\" ] && [ -z \"$__status\" ]; then\n    printf -v \"$__result\" '%s' \"$lastversion\"\n  else\n    local __buildinfo\n    if ! __buildinfo=$(git rev-parse --short HEAD 2>/dev/null); then\n      error_exit \"Failed to get short commit hash\"\n    fi\n\n    local __currentbranch\n    if ! __currentbranch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null); then\n      error_exit \"Failed to get current branch name\"\n    fi\n    # Safely truncate branch name\n    __currentbranch=\"${__currentbranch:0:$MAX_BRANCH_LENGTH}\"\n\n    local default_branch\n    get_default_branch default_branch\n    if [ \"$__currentbranch\" != \"$default_branch\" ]; then\n      __buildinfo=\"$__currentbranch.$__buildinfo\"\n    fi\n\n    local __suffix=\"\"\n    if [ \"$__commitcount\" != \"0\" ]; then\n      if [ -n \"$__suffix\" ]; then\n        __suffix=\"$__suffix.\"\n      fi\n      __suffix=\"$__suffix$__commitcount\"\n    fi\n    if [ -n \"$__status\" ]; then\n      if [ -n \"$__suffix\" ]; then\n        __suffix=\"$__suffix.\"\n      fi\n      __suffix=\"$__suffix$__status\"\n    fi\n\n    __suffix=\"$__suffix+$__buildinfo\"\n    if [ \"$lastversion\" == \"$finalversion\" ]; then\n      scope=\"patch\"\n      identifier=\"\"\n      local __bumped=\"\"\n      bump_version __bumped\n      printf -v \"$__result\" '%s' \"$__bumped-dev.$__suffix\"\n    else\n      printf -v \"$__result\" '%s' \"$lastversion.$__suffix\"\n    fi\n  fi\n}\n\nfunction init {\n  check_git_repo\n\n  local TAGS\n  if ! TAGS=$(git tag --merged 2>/dev/null); then\n    error_exit \"Failed to get git tags\"\n  fi\n\n  local TAG_ARRAY\n  if [[ -n \"$TAGS\" ]]; then\n    IFS=$'\\n' read -rd '' -a TAG_ARRAY <<<\"$TAGS\" || true\n  else\n    TAG_ARRAY=()\n  fi\n\n  # Filter and strip tag_prefix from tags\n  local FILTERED_TAGS=()\n  if [[ ${#TAG_ARRAY[@]} -gt 0 ]]; then\n    for tag in \"${TAG_ARRAY[@]}\"; do\n      if [[ \"$tag\" == \"$tag_prefix\"* ]]; then\n        # Strip the tag_prefix from the tag\n        local stripped_tag=\"${tag#$tag_prefix}\"\n        FILTERED_TAGS+=(\"$stripped_tag\")\n      fi\n    done\n  fi\n\n  if [[ ${#FILTERED_TAGS[@]} -gt 0 ]]; then\n    get_latest \"${FILTERED_TAGS[@]}\"\n  else\n    get_latest\n  fi\n\n  if ! currentbranch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null); then\n    error_exit \"Failed to get current git branch\"\n  fi\n}\n\ncase \"$ACTION\" in\n  --help)\n    echo -e \"$HELP\"\n    ;;\n  --version)\n    echo -e \"${PROG}: $PROG_VERSION\"\n    ;;\n  final)\n    init\n    default_branch=\"\"\n    get_default_branch default_branch\n    diff=\"\"\n    if ! diff=$(git diff \"$default_branch\" 2>/dev/null); then\n      error_exit \"Failed to compare with default branch: $default_branch\"\n    fi\n    if [ \"$forcetag\" == \"false\" ]; then\n      if [ -n \"$diff\" ]; then\n        error_exit \"Branch must be updated with $default_branch for final versions\"\n      fi\n    fi\n    increase_version\n    ;;\n  alpha|beta)\n    init\n    identifier=\"$ACTION\"\n    increase_version\n    ;;\n  candidate)\n    init\n    identifier=\"rc\"\n    increase_version\n    ;;\n  getlast)\n    init\n    echo \"$tag_prefix$lastversion\"\n    ;;\n  getfinal)\n    init\n    echo \"$tag_prefix$finalversion\"\n    ;;\n  getcurrent)\n    init\n    current=\"\"\n    get_current current\n    echo \"$current\"\n    ;;\n  get)\n    init\n    echo \"Current final version: $tag_prefix$finalversion\"\n    echo \"Last tagged version:   $tag_prefix$lastversion\"\n    ;;\n  *)\n    error_exit \"'$ACTION' is not a valid command, see --help for available commands.\"\n    ;;\nesac\n"
  },
  {
    "path": "semtag_bkp",
    "content": "#!/usr/bin/env bash\n\n# This is a deprecated version\n\nPROG=semtag\nPROG_VERSION=\"v0.1.2\"\n\nSEMVER_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-]+)*)?$\"\nIDENTIFIER_REGEX=\"^\\-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*$\"\nNUMERIC_REGEX='^[0-9]+$'\n\n# Length limit for the branch name in build metadata.\nMAX_BRANCH_LENGTH=50\n\n# Global variables\nFIRST_VERSION=\"v0.0.0\"\nfinalversion=$FIRST_VERSION\nlastversion=$FIRST_VERSION\nhasversiontag=\"false\"\nscope=\"patch\"\ndisplayonly=\"false\"\nforcetag=\"false\"\nprefix=\"v\"\nforcedversion=\nversionname=\nidentifier=\n\nHELP=\"\\\nUsage:\n  $PROG\n  $PROG getlast\n  $PROG getfinal\n  $PROG (final|alpha|beta|candidate) [-s <scope> (major|minor|patch|auto) | -o]\n  $PROG --help\n  $PROG --version\nOptions:\n  -s         The scope that must be increased, can be major, minor or patch.\n               The resulting version will match X.Y.Z(-PRERELEASE)(+BUILD)\n               where X, Y and Z are positive integers, PRERELEASE is an optionnal\n               string composed of alphanumeric characters describing if the build is\n               a release candidate, alpha or beta version, with a number.\n               BUILD is also an optional string composed of alphanumeric\n               characters and hyphens.\n               Setting the scope as 'auto', the script will chose the scope between\n               'minor' and 'patch', depending on the amount of lines added (<10% will\n               choose patch).\n  -v         Specifies manually the version to be tagged, must be a valid semantic version\n               in the format X.Y.Z where X, Y and Z are positive integers.\n  -o         Output the version only, shows the bumped version, but doesn't tag.\n  -f         Forces to tag, even if there are unstaged or uncommited changes.\n  -p         Use a plain version, ie. do not prefix with 'v'.\nCommands:\n  --help     Print this help message.\n  --version  Prints the program's version.\n  get        Returns both current final version and last tagged version.\n  getlast    Returns the latest tagged version.\n  getfinal   Returns the latest tagged final version.\n  getcurrent Returns the current version, based on the latest one, if there are uncommited or\n               unstaged changes, they will be reflected in the version, adding the number of\n               pending commits, current branch and commit hash.\n  final      Tags the current build as a final version, this only can be done on the default branch.\n  candidate  Tags the current build as a release candidate, the tag will contain all\n               the commits from the last final version.\n  alpha      Tags the current build as an alpha version, the tag will contain all\n               the commits from the last final version.\n  beta       Tags the current build as a beta version, the tag will contain all\n               the commits from the last final version.\"\n\n# Commands and options\nACTION=\"getlast\"\nACTION=\"$1\"\nshift\n\n# We get the parameters\nwhile getopts \"v:s:ofp\" opt; do\n  case $opt in\n    v)\n      forcedversion=\"$OPTARG\"\n      ;;\n    s)\n      scope=\"$OPTARG\"\n      ;;\n    o)\n      displayonly=\"true\"\n      ;;\n    f)\n      forcetag=\"true\"\n      ;;\n    p)\n      prefix=\"\"\n      ;;\n    \\?)\n      echo \"Invalid option: -$OPTARG\" >&2\n      exit 1\n      ;;\n    :)\n      echo \"Option -$OPTARG requires an argument.\" >&2\n      exit 1\n      ;;\n  esac\ndone\n\n# Try to programmatically fetch the default branch. Go by the first remote HEAD found, otherwise default to `master`.\n# $1 The variable to store the result\nfunction get_default_branch {\n  local __result=$1\n\n  local __remotes=$(git remote)\n  if [[ -n $__remotes ]]; then\n    for __remote in $__remotes; do\n      local __default_branch_ref=$(git symbolic-ref --quiet refs/remotes/${__remote}/HEAD || true)\n      local __default_branch=${__default_branch_ref#refs/remotes/${__remote}/}\n      if [[ -n ${__default_branch} ]]; then\n        break\n      fi\n    done\n  fi\n\n  eval \"${__result}=${__default_branch:-master}\"\n}\n\n# Gets a string with the version and returns an array of maximum size of 5 with all the parts of the sematinc version\n# $1 The string containing the version in semantic format\n# $2 The variable to store the result array:\n#      position 0: major number\n#      position 1: minor number\n#      position 2: patch number\n#      position 3: identifier (or prerelease identifier)\n#      position 4: build info\nfunction explode_version {\n  local __version=$1\n  local __result=$2\n  if [[ $__version =~ $SEMVER_REGEX ]] ; then\n    local __major=${BASH_REMATCH[1]}\n    local __minor=${BASH_REMATCH[2]}\n    local __patch=${BASH_REMATCH[3]}\n    local __prere=${BASH_REMATCH[4]}\n    local __build=${BASH_REMATCH[5]}\n    eval \"$__result=(\\\"$__major\\\" \\\"$__minor\\\" \\\"$__patch\\\" \\\"$__prere\\\" \\\"$__build\\\")\"\n  else\n    eval \"$__result=\"\n  fi\n}\n\n# Compare two versions and returns -1, 0 or 1\n# $1 The first version to compare\n# $2 The second version to compare\n# $3 The variable where to store the result\nfunction compare_versions {\n  local __first\n  local __second\n  explode_version $1 __first\n  explode_version $2 __second\n  local lv=$3\n\n  # Compares MAJOR, MINOR and PATCH\n  for i in 0 1 2; do\n    local __numberfirst=${__first[$i]}\n    local __numbersecond=${__second[$i]}\n    case $(($__numberfirst - $__numbersecond)) in\n      0)\n        ;;\n      -[0-9]*)\n        eval \"$lv=-1\"\n        return 0\n        ;;\n      [0-9]*)\n        eval \"$lv=1\"\n        return 0\n        ;;\n    esac\n  done\n\n  # Identifiers should compare with the ASCII order.\n  local __identifierfirst=${__first[3]}\n  local __identifiersecond=${__second[3]}\n  compare_identifiers \"${__first[3]}\" \"${__second[3]}\" compareresult\n  eval \"$lv=$compareresult\"\n}\n\n\n# Returns the number comparison\n# $1 The first number to compare\n# $2 The second number to compare\n# $3 The variable where to store the result\nfunction compare_numeric {\n  local __first=$1\n  local __second=$2\n  local __result=$3\n\n  if (( \"$__first\" < \"$__second\" )) ; then\n    eval \"$__result=-1\"\n  elif (( \"$__first\" > \"$__second\" )) ; then\n    eval \"$__result=1\"\n  else\n    eval \"$__result=0\"\n  fi\n}\n\n# Returns the alpanumeric comparison\n# $1 The first alpanumeric to compare\n# $2 The second alpanumeric to compare\n# $3 The variable where to store the result\nfunction compare_alphanumeric {\n  local __first=$1\n  local __second=$2\n  local __result=$3\n\n  if [[ \"$__first\" < \"$__second\" ]] ; then\n    eval \"$__result=-1\"\n  elif [[ \"$__first\" > \"$__second\" ]] ; then\n    eval \"$__result=1\"\n  else\n    eval \"$__result=0\"\n  fi\n}\n\n# Returns the last version of two\n# $1 The first version to compare\n# $2 The second version to compare\n# $3 The variable where to store the last one\nfunction get_latest_of_two {\n  local __first=$1\n  local __second=$2\n  local __result\n  local __latest=$3\n\n  compare_versions $__first $__second __result\n  case $__result in\n    0)\n      eval \"$__latest=$__second\"\n      ;;\n    -1)\n      eval \"$__latest=$__second\"\n      ;;\n    1)\n      eval \"$__latest=$__first\"\n      ;;\n  esac\n}\n\n# Returns comparison of two identifier parts\n# $1 The first part to compare\n# $2 The second part to compare\n# $3 The variable where to store the compare result\nfunction compare_identifier_part {\n  local __first=$1\n  local __second=$2\n  local __result=$3\n  local compareresult\n\n  if [[ \"$__first\" =~ $NUMERIC_REGEX ]] && [[ \"$__second\" =~ $NUMERIC_REGEX ]] ; then\n    compare_numeric \"$__first\" \"$__second\" compareresult\n    eval \"$__result=$compareresult\"\n    return 0\n  fi\n\n\n  compare_alphanumeric \"$__first\" \"$__second\" compareresult\n  eval \"$__result=$compareresult\"\n}\n\n# Returns comparison of two identifiers\n# $1 The first identifier to compare\n# $2 The second identifier to compare\n# $3 The variable where to store the compare result\nfunction compare_identifiers {\n  local __first=$1\n  local __second=$2\n  local __result=$3\n  local partresult\n  local arraylengths\n  if [[ -n \"$__first\" ]] && [[ -n \"$__second\" ]]; then\n    explode_identifier \"${__first}\" explodedidentifierfirst\n    explode_identifier \"${__second}\" explodedidentifiersecond\n\n    firstsize=${#explodedidentifierfirst[@]}\n    secondsize=${#explodedidentifiersecond[@]}\n    minlength=$(( $firstsize<$secondsize ? $firstsize : $secondsize ))\n    for (( i = 0 ; i < $minlength ; i++ )); do\n      compare_identifier_part \"${explodedidentifierfirst[$i]}\" \"${explodedidentifiersecond[$i]}\" partresult\n      case $partresult in\n        0)\n          ;;\n        *)\n          eval \"$__result=$partresult\"\n          return 0\n          ;;\n      esac\n    done\n    compare_numeric $firstsize $secondsize arraylengths\n    eval \"$__result=$arraylengths\"\n    return 0\n  elif [[ -z \"$__first\" ]] && [[ -n \"$__second\" ]]; then\n    eval \"$__result=1\"\n    return 0\n  elif [[ -n \"$__first\" ]] && [[ -z \"$__second\" ]]; then\n    eval \"$__result=-1\"\n    return 0\n  fi\n\n  eval \"$__result=0\"\n}\n\n# Assigns a 2 size array with the identifier, having the identifier at pos 0, and the number in pos 1\n# $1 The identifier in the format -id.#\n# $2 The vferiable where to store the 2 size array\nfunction explode_identifier {\n  local __identifier=$1\n  local __result=$2\n  if [[ $__identifier =~ $IDENTIFIER_REGEX ]] ; then\n    IFS='-.' read -ra identifierparts <<< $__identifier\n    eval \"$__result=( ${identifierparts[@]} )\"\n  else\n    eval \"$__result=\"\n  fi\n}\n\n# Gets a list of tags and assigns the base and latest versions\n# Receives an array with the tags containing the versions\n# Assigns to the global variables finalversion and lastversion the final version and the latest version\nfunction get_latest {\n  local __taglist=(\"$@\")\n  local __tagsnumber=${#__taglist[@]}\n  local __current\n  case $__tagsnumber in\n    0)\n      finalversion=$FIRST_VERSION\n      lastversion=$FIRST_VERSION\n      ;;\n    1)\n      __current=${__taglist[0]}\n      explode_version $__current ver\n      if [ -n \"$ver\" ]; then\n        if [ -n \"${ver[3]}\" ]; then\n          finalversion=$FIRST_VERSION\n        else\n          finalversion=$__current\n        fi\n        lastversion=$__current\n      else\n        finalversion=$FIRST_VERSION\n        lastversion=$FIRST_VERSION\n      fi\n      ;;\n    *)\n      local __lastpos=$(($__tagsnumber-1))\n      for i in $(seq 0 $__lastpos)\n      do\n        __current=${__taglist[i]}\n        explode_version ${__taglist[i]} ver\n        if [ -n \"$ver\" ]; then\n          if [ -z \"${ver[3]}\" ]; then\n            get_latest_of_two $finalversion $__current finalversion\n            get_latest_of_two $lastversion $finalversion lastversion\n          else\n            get_latest_of_two $lastversion $__current lastversion\n          fi\n        fi\n      done\n      ;;\n  esac\n\n  if git rev-parse -q --verify \"refs/tags/$lastversion\" >/dev/null; then\n    hasversiontag=\"true\"\n  else\n    hasversiontag=\"false\"\n  fi\n}\n\n# Gets the next version given the provided scope\n# $1 The version that is going to be bumped\n# $2 The scope to bump\n# $3 The variable where to stoer the result\nfunction get_next_version {\n  local __exploded\n  local __fromversion=$1\n  local __scope=$2\n  local __result=$3\n  explode_version $__fromversion __exploded\n  case $__scope in\n    major)\n      __exploded[0]=$((${__exploded[0]}+1))\n      __exploded[1]=0\n      __exploded[2]=0\n    ;;\n    minor)\n      __exploded[1]=$((${__exploded[1]}+1))\n      __exploded[2]=0\n    ;;\n    patch)\n      __exploded[2]=$((${__exploded[2]}+1))\n    ;;\n  esac\n\n  eval \"$__result=${prefix}${__exploded[0]}.${__exploded[1]}.${__exploded[2]}\"\n}\n\nfunction bump_version {\n  ## First we try to get the next version based on the existing last one\n  if [ \"$scope\" == \"auto\" ]; then\n    get_scope_auto scope\n  fi\n\n  local __candidatefromlast=$FIRST_VERSION\n  local __explodedlast\n  explode_version $lastversion __explodedlast\n  if [[ -n \"${__explodedlast[3]}\" ]]; then\n    # Last version is not final\n    local __idlast\n    explode_identifier ${__explodedlast[3]} __idlast\n\n    # We get the last, given the desired id based on the scope\n    __candidatefromlast=\"${prefix}${__explodedlast[0]}.${__explodedlast[1]}.${__explodedlast[2]}\"\n    if [[ -n \"$identifier\" ]]; then\n      local __nextid=\"$identifier.1\"\n      if [ \"$identifier\" == \"${__idlast[0]}\" ]; then\n        # We target the same identifier as the last so we increase one\n        __nextid=\"$identifier.$(( ${__idlast[1]}+1 ))\"\n        __candidatefromlast=\"$__candidatefromlast-$__nextid\"\n      else\n        # Different identifiers, we make sure we are assigning a higher identifier, if not, we increase the version\n        __candidatefromlast=\"$__candidatefromlast-$__nextid\"\n        local __comparedwithlast\n        compare_versions $__candidatefromlast $lastversion __comparedwithlast\n        if [ \"$__comparedwithlast\" == -1 ]; then\n          get_next_version $__candidatefromlast $scope __candidatefromlast\n          __candidatefromlast=\"$__candidatefromlast-$__nextid\"\n        fi\n      fi\n    fi\n  fi\n\n  # Then we try to get the version based on the latest final one\n  local __candidatefromfinal=$FIRST_VERSION\n  get_next_version $finalversion $scope __candidatefromfinal\n  if [[ -n \"$identifier\" ]]; then\n    __candidatefromfinal=\"$__candidatefromfinal-$identifier.1\"\n  fi\n\n  # Finally we compare both candidates\n  local __resultversion\n  local __result\n  compare_versions $__candidatefromlast $__candidatefromfinal __result\n  case $__result in\n    0)\n      __resultversion=$__candidatefromlast\n      ;;\n    -1)\n      __resultversion=\"$__candidatefromfinal\"\n      ;;\n    1)\n      __resultversion=$__candidatefromlast\n      ;;\n  esac\n\n  eval \"$1=$__resultversion\"\n}\n\nfunction increase_version {\n  local __version=\n\n  if [ -z $forcedversion ]; then\n    bump_version __version\n  else\n    if [[ $forcedversion =~ $SEMVER_REGEX ]] ; then\n      compare_versions $forcedversion $lastversion __result\n      if [ $__result -le 0 ]; then\n        echo \"Version can't be lower than last version: $lastversion\"\n        exit 1\n      fi\n    else\n      echo \"Non valid version to bump\"\n      exit 1\n    fi\n    __version=$forcedversion\n  fi\n\n  if [ \"$displayonly\" == \"true\" ]; then\n    echo \"$__version\"\n  else\n    if [ \"$forcetag\" == \"false\" ]; then\n      check_git_dirty_status\n    fi\n    local __commitlist\n    if [ \"$finalversion\" == \"$FIRST_VERSION\" ] || [ \"$hasversiontag\" != \"true\" ]; then\n      __commitlist=\"$(git log --pretty=oneline | cat)\"\n    else\n      __commitlist=\"$(git log --pretty=oneline $finalversion... | cat)\"\n    fi\n\n    # If we are forcing a bump, we add bump to the commit list\n    if [[ -z $__commitlist && \"$forcetag\" == \"true\" ]]; then\n      __commitlist=\"bump\"\n    fi\n\n    if [[ -z $__commitlist ]]; then\n      echo \"No commits since the last final version, not bumping version\"\n    else\n      if [[ -z $versionname ]]; then\n        versionname=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\n      fi\n      local __message=\"$versionname\n$__commitlist\"\n\n      # We check we have info on the user\n      local __username=$(git config user.name)\n      if [ -z \"$__username\" ]; then\n        __username=$(id -u -n)\n        git config user.name $__username\n      fi\n      local __useremail=$(git config user.email)\n      if [ -z \"$__useremail\" ]; then\n        __useremail=$(hostname)\n        git config user.email \"$__username@$__useremail\"\n      fi\n\n      git tag -a $__version -m \"$__message\"\n\n      # If we have a remote, we push there\n      local __remotes=$(git remote)\n      if [[ -n $__remotes ]]; then\n        for __remote in $__remotes; do\n          git push $__remote $__version > /dev/null\n          if [ $? -eq 0 ]; then\n            echo \"$__version pushed to $__remote\"\n          else\n            echo \"Error pushing the tag $__version to $__remote\"\n            exit 1\n          fi\n        done\n      else\n        echo \"$__version\"\n      fi\n    fi\n  fi\n}\n\nfunction check_git_dirty_status {\n  local __repostatus=\n  get_work_tree_status __repostatus\n\n  if [ \"$__repostatus\" == \"uncommitted\" ]; then\n    echo \"ERROR: You have uncommitted changes\"\n    git status --porcelain\n    exit 1\n  fi\n\n  if [ \"$__repostatus\" == \"unstaged\" ]; then\n    echo \"ERROR: You have unstaged changes\"\n    git status --porcelain\n    exit 1\n  fi\n}\n\n# Get the total amount of lines of code in the repo\nfunction get_total_lines {\n  local __empty_id=\"$(git hash-object -t tree /dev/null)\"\n  local __changes=\"$(git diff --numstat $__empty_id | cat)\"\n  local __added_deleted=$1\n  get_changed_lines \"$__changes\" $__added_deleted\n}\n\n# Get the total amount of lines of code since the provided tag\nfunction get_sincetag_lines {\n  local __sincetag=$1\n  local __changes=\"$(git diff --numstat $__sincetag | cat)\"\n  local __added_deleted=$2\n  get_changed_lines \"$__changes\" $__added_deleted\n}\n\nfunction get_changed_lines {\n  local __changes_numstat=$1\n  local __result=$2\n  IFS=$'\\n' read -rd '' -a __changes_array <<<\"$__changes_numstat\"\n  local __diff_regex=\"^([0-9]+)[[:space:]]+([0-9]+)[[:space:]]+.+$\"\n\n  local __total_added=0\n  local __total_deleted=0\n  for i in \"${__changes_array[@]}\"\n  do\n    if [[ $i =~ $__diff_regex ]] ; then\n      local __added=${BASH_REMATCH[1]}\n      local __deleted=${BASH_REMATCH[2]}\n      __total_added=$(( $__total_added+$__added ))\n      __total_deleted=$(( $__total_deleted+$__deleted ))\n    fi\n  done\n  eval \"$2=( $__total_added $__total_deleted )\"\n}\n\nfunction get_scope_auto {\n  local __verbose=$2\n  local __total=0\n  local __since=0\n  local __scope=\n\n  get_total_lines __total\n  get_sincetag_lines $finalversion __since\n\n  local __percentage=0\n  if [ \"$__total\" != \"0\" ]; then\n    local __percentage=$(( 100*$__since/$__total ))\n    if [ $__percentage -gt \"10\" ]; then\n      __scope=\"minor\"\n    else\n      __scope=\"patch\"\n    fi\n  fi\n\n  eval \"$1=$__scope\"\n  if [[ -n \"$__verbose\" ]]; then\n    echo \"[Auto Scope] Percentage of lines changed: $__percentage\"\n    echo \"[Auto Scope] : $__scope\"\n  fi\n}\n\nfunction get_work_tree_status {\n  # Update the index\n  git update-index -q --ignore-submodules --refresh > /dev/null\n  eval \"$1=\"\n\n  if ! git diff-files --quiet --ignore-submodules -- > /dev/null\n  then\n    eval \"$1=unstaged\"\n  fi\n\n  if ! git diff-index --cached --quiet HEAD --ignore-submodules -- > /dev/null\n  then\n    eval \"$1=uncommitted\"\n  fi\n}\n\nfunction get_current {\n  if [ \"$hasversiontag\" == \"true\" ]; then\n    local __commitcount=\"$(git rev-list $lastversion.. --count)\"\n  else\n    local __commitcount=\"$(git rev-list --count HEAD)\"\n  fi\n  local __status=\n  get_work_tree_status __status\n\n  if [ \"$__commitcount\" == \"0\" ] && [ -z \"$__status\" ]; then\n    eval \"$1=$lastversion\"\n  else\n    local __buildinfo=\"$(git rev-parse --short HEAD)\"\n    local __currentbranch=\"$(git rev-parse --abbrev-ref HEAD | cut -c1-$MAX_BRANCH_LENGTH)\"\n    get_default_branch default_branch\n    if [ \"$__currentbranch\" != \"master\" ]; then\n      __buildinfo=\"$__currentbranch.$__buildinfo\"\n    fi\n\n    local __suffix=\n    if [ \"$__commitcount\" != \"0\" ]; then\n      if [ -n \"$__suffix\" ]; then\n        __suffix=\"$__suffix.\"\n      fi\n      __suffix=\"$__suffix$__commitcount\"\n    fi\n    if [ -n \"$__status\" ]; then\n      if [ -n \"$__suffix\" ]; then\n        __suffix=\"$__suffix.\"\n      fi\n      __suffix=\"$__suffix$__status\"\n    fi\n\n    __suffix=\"$__suffix+$__buildinfo\"\n    if [ \"$lastversion\" == \"$finalversion\" ]; then\n      scope=\"patch\"\n      identifier=\n      local __bumped=\n      bump_version __bumped\n      eval \"$1=$__bumped-dev.$__suffix\"\n    else\n      eval \"$1=$lastversion.$__suffix\"\n    fi\n  fi\n}\n\nfunction init {\n  TAGS=\"$(git tag --merged)\"\n  IFS=$'\\n' read -rd '' -a TAG_ARRAY <<<\"$TAGS\"\n\n  get_latest ${TAG_ARRAY[@]}\n  currentbranch=\"$(git rev-parse --abbrev-ref HEAD)\"\n}\n\ncase $ACTION in\n  --help)\n    echo -e \"$HELP\"\n    ;;\n  --version)\n    echo -e \"${PROG}: $PROG_VERSION\"\n    ;;\n  final)\n    init\n    get_default_branch default_branch\n    diff=$(git diff $default_branch | cat)\n    if [ \"$forcetag\" == \"false\" ]; then\n      if [ -n \"$diff\" ]; then\n        echo \"ERROR: Branch must be updated with $default_branch for final versions\"\n        exit 1\n      fi\n    fi\n    increase_version\n    ;;\n  alpha|beta)\n    init\n    identifier=\"$ACTION\"\n    increase_version\n    ;;\n  candidate)\n    init\n    identifier=\"rc\"\n    increase_version\n    ;;\n  getlast)\n    init\n    echo \"$lastversion\"\n    ;;\n  getfinal)\n    init\n    echo \"$finalversion\"\n    ;;\n  getcurrent)\n    init\n    get_current current\n    echo \"$current\"\n    ;;\n  get)\n    init\n    echo \"Current final version: $finalversion\"\n    echo \"Last tagged version:   $lastversion\"\n    ;;\n  *)\n    echo \"'$ACTION' is not a valid command, see --help for available commands.\"\n    ;;\nesac\n\n"
  },
  {
    "path": "test_semtag.sh",
    "content": "#!/usr/bin/env bash\n\n# Test script for semtag beta/alpha/rc increment functionality\n# This script validates that pre-release version increments work correctly\n\nset -euo pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nSEMTAG=\"$SCRIPT_DIR/semtag\"\n\n# Colors for output\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nNC='\\033[0m' # No Color\n\n# Test counters\nTESTS_RUN=0\nTESTS_PASSED=0\nTESTS_FAILED=0\n\n# Array to store created tags for cleanup\nCREATED_TAGS=()\n\n# Function to print test results\nprint_result() {\n    local test_name=\"$1\"\n    local expected=\"$2\"\n    local actual=\"$3\"\n\n    TESTS_RUN=$((TESTS_RUN + 1))\n\n    if [ \"$expected\" = \"$actual\" ]; then\n        echo -e \"${GREEN}✓${NC} $test_name\"\n        echo \"  Expected: $expected\"\n        echo \"  Got:      $actual\"\n        TESTS_PASSED=$((TESTS_PASSED + 1))\n    else\n        echo -e \"${RED}✗${NC} $test_name\"\n        echo \"  Expected: $expected\"\n        echo \"  Got:      $actual\"\n        TESTS_FAILED=$((TESTS_FAILED + 1))\n    fi\n    echo\n}\n\n# Function to create a test tag\ncreate_tag() {\n    local tag=\"$1\"\n    git tag \"$tag\" -m \"test tag: $tag\" >/dev/null 2>&1\n    CREATED_TAGS+=(\"$tag\")\n}\n\n# Function to delete test tags\ncleanup_tags() {\n    if [ ${#CREATED_TAGS[@]} -gt 0 ]; then\n        echo -e \"${YELLOW}Cleaning up test tags...${NC}\"\n        for tag in \"${CREATED_TAGS[@]}\"; do\n            git tag -d \"$tag\" >/dev/null 2>&1 || true\n        done\n        CREATED_TAGS=()\n    fi\n}\n\n# Cleanup on exit\ntrap cleanup_tags EXIT\n\necho \"================================================\"\necho \"Semtag Pre-release Version Increment Tests\"\necho \"================================================\"\necho\n\n# Get the current state\nINITIAL_LAST=$(\"$SEMTAG\" getlast)\nINITIAL_FINAL=$(\"$SEMTAG\" getfinal)\necho \"Initial state:\"\necho \"  Last version:  $INITIAL_LAST\"\necho \"  Final version: $INITIAL_FINAL\"\necho\n\n# Test 1: Beta with patch scope - first beta\necho \"=== Test 1: Beta Patch Increment ===\"\nresult=$(\"$SEMTAG\" beta -s patch -o)\nprint_result \"First beta with patch scope\" \"v0.2.1-beta.1\" \"$result\"\n\n# Test 2: Beta with patch scope - increment beta number\ncreate_tag \"v0.2.1-beta.1\"\nresult=$(\"$SEMTAG\" beta -s patch -o)\nprint_result \"Increment beta.1 to beta.2\" \"v0.2.1-beta.2\" \"$result\"\n\n# Test 3: Beta with patch scope - increment beta number again\ncreate_tag \"v0.2.1-beta.2\"\nresult=$(\"$SEMTAG\" beta -s patch -o)\nprint_result \"Increment beta.2 to beta.3\" \"v0.2.1-beta.3\" \"$result\"\n\ncleanup_tags\n\n# Test 4: Beta with minor scope - first beta\necho \"=== Test 2: Beta Minor Increment ===\"\nresult=$(\"$SEMTAG\" beta -s minor -o)\nprint_result \"First beta with minor scope\" \"v0.3.0-beta.1\" \"$result\"\n\n# Test 5: Beta with minor scope - increment beta number\ncreate_tag \"v0.3.0-beta.1\"\nresult=$(\"$SEMTAG\" beta -s minor -o)\nprint_result \"Increment beta.1 to beta.2 (minor)\" \"v0.3.0-beta.2\" \"$result\"\n\n# Test 6: Beta with minor scope - increment beta number again\ncreate_tag \"v0.3.0-beta.2\"\nresult=$(\"$SEMTAG\" beta -s minor -o)\nprint_result \"Increment beta.2 to beta.3 (minor)\" \"v0.3.0-beta.3\" \"$result\"\n\ncleanup_tags\n\n# Test 7: Beta with major scope - first beta\necho \"=== Test 3: Beta Major Increment ===\"\nresult=$(\"$SEMTAG\" beta -s major -o)\nprint_result \"First beta with major scope\" \"v1.0.0-beta.1\" \"$result\"\n\n# Test 8: Beta with major scope - increment beta number\ncreate_tag \"v1.0.0-beta.1\"\nresult=$(\"$SEMTAG\" beta -s major -o)\nprint_result \"Increment beta.1 to beta.2 (major)\" \"v1.0.0-beta.2\" \"$result\"\n\ncleanup_tags\n\n# Test 9: Alpha increments\necho \"=== Test 4: Alpha Increment ===\"\nresult=$(\"$SEMTAG\" alpha -s patch -o)\nprint_result \"First alpha with patch scope\" \"v0.2.1-alpha.1\" \"$result\"\n\ncreate_tag \"v0.2.1-alpha.1\"\nresult=$(\"$SEMTAG\" alpha -s patch -o)\nprint_result \"Increment alpha.1 to alpha.2\" \"v0.2.1-alpha.2\" \"$result\"\n\ncleanup_tags\n\n# Test 10: Release candidate increments\necho \"=== Test 5: Release Candidate Increment ===\"\nresult=$(\"$SEMTAG\" candidate -s patch -o)\nprint_result \"First rc with patch scope\" \"v0.2.1-rc.1\" \"$result\"\n\ncreate_tag \"v0.2.1-rc.1\"\nresult=$(\"$SEMTAG\" candidate -s patch -o)\nprint_result \"Increment rc.1 to rc.2\" \"v0.2.1-rc.2\" \"$result\"\n\ncleanup_tags\n\n# Test 11: Switching between identifiers\necho \"=== Test 6: Identifier Switching ===\"\ncreate_tag \"v0.3.0-alpha.5\"\nresult=$(\"$SEMTAG\" beta -s minor -o)\nprint_result \"Switch from alpha to beta (same version)\" \"v0.3.0-beta.1\" \"$result\"\n\ncreate_tag \"v0.3.0-beta.1\"\nresult=$(\"$SEMTAG\" alpha -s minor -o)\nprint_result \"Switch from beta to alpha (bumps version)\" \"v0.4.0-alpha.1\" \"$result\"\n\ncleanup_tags\n\n# Test 12: Multiple sequential increments\necho \"=== Test 7: Sequential Beta Increments ===\"\ncreate_tag \"v0.2.1-beta.1\"\ncreate_tag \"v0.2.1-beta.2\"\ncreate_tag \"v0.2.1-beta.3\"\nresult=$(\"$SEMTAG\" beta -s patch -o)\nprint_result \"Increment beta.3 to beta.4\" \"v0.2.1-beta.4\" \"$result\"\n\ncleanup_tags\n\n# Test 13: Final version with patch scope\necho \"=== Test 8: Final Version Patch Increment ===\"\n# Start from v0.2.0, increment patch\nresult=$(\"$SEMTAG\" final -s patch -o -f)\nprint_result \"Final version patch increment\" \"v0.2.1\" \"$result\"\n\n# Test 14: Final version with minor scope\necho \"=== Test 9: Final Version Minor Increment ===\"\nresult=$(\"$SEMTAG\" final -s minor -o -f)\nprint_result \"Final version minor increment\" \"v0.3.0\" \"$result\"\n\n# Test 15: Final version with major scope\necho \"=== Test 10: Final Version Major Increment ===\"\nresult=$(\"$SEMTAG\" final -s major -o -f)\nprint_result \"Final version major increment\" \"v1.0.0\" \"$result\"\n\n# Test 16: Sequential final versions\necho \"=== Test 11: Sequential Final Version Increments ===\"\ncreate_tag \"v0.2.1\"\nresult=$(\"$SEMTAG\" final -s patch -o -f)\nprint_result \"Increment v0.2.1 to v0.2.2\" \"v0.2.2\" \"$result\"\n\ncreate_tag \"v0.2.2\"\nresult=$(\"$SEMTAG\" final -s patch -o -f)\nprint_result \"Increment v0.2.2 to v0.2.3\" \"v0.2.3\" \"$result\"\n\ncleanup_tags\n\n# Test 17: Going from pre-release to final\necho \"=== Test 12: Pre-release to Final Version ===\"\ncreate_tag \"v0.3.0-beta.3\"\nresult=$(\"$SEMTAG\" final -s minor -o -f)\nprint_result \"From beta.3 to final minor version\" \"v0.3.0\" \"$result\"\n\ncreate_tag \"v0.4.0-rc.2\"\nresult=$(\"$SEMTAG\" final -s minor -o -f)\nprint_result \"From rc.2 to final minor version\" \"v0.4.0\" \"$result\"\n\ncleanup_tags\n\n# Test 18: Mixed major/minor/patch with pre-releases\necho \"=== Test 13: Mixed Version Scenarios ===\"\ncreate_tag \"v1.5.3\"\nresult=$(\"$SEMTAG\" beta -s patch -o)\nprint_result \"From v1.5.3 to beta patch\" \"v1.5.4-beta.1\" \"$result\"\n\ncreate_tag \"v1.5.4-beta.1\"\ncreate_tag \"v1.5.4-beta.2\"\nresult=$(\"$SEMTAG\" final -s patch -o -f)\nprint_result \"From beta.2 to final patch\" \"v1.5.4\" \"$result\"\n\ncleanup_tags\n\n# Test 19: Major version with pre-releases\necho \"=== Test 14: Major Version with Pre-releases ===\"\ncreate_tag \"v2.0.0\"\nresult=$(\"$SEMTAG\" beta -s major -o)\nprint_result \"From v2.0.0 to major beta\" \"v3.0.0-beta.1\" \"$result\"\n\ncreate_tag \"v3.0.0-beta.1\"\nresult=$(\"$SEMTAG\" final -s major -o -f)\nprint_result \"From beta.1 to final major\" \"v3.0.0\" \"$result\"\n\ncleanup_tags\n\n# Print summary\necho \"================================================\"\necho \"Test Summary\"\necho \"================================================\"\necho -e \"Tests run:    $TESTS_RUN\"\necho -e \"${GREEN}Tests passed: $TESTS_PASSED${NC}\"\nif [ $TESTS_FAILED -gt 0 ]; then\n    echo -e \"${RED}Tests failed: $TESTS_FAILED${NC}\"\nelse\n    echo -e \"${GREEN}Tests failed: $TESTS_FAILED${NC}\"\nfi\necho \"================================================\"\n\n# Exit with failure if any tests failed\nif [ $TESTS_FAILED -gt 0 ]; then\n    exit 1\nelse\n    echo -e \"${GREEN}All tests passed!${NC}\"\n    exit 0\nfi\n"
  }
]