├── .gitignore ├── .github ├── CODEOWNERS └── workflows │ ├── create_issue_on_label.yml │ ├── create_issue.yml │ └── comment_issue.yml ├── spec ├── utils.cr └── git-version-spec.cr ├── Dockerfile ├── Makefile ├── src ├── main.cr └── git-version.cr ├── action.yml ├── .circleci └── config.yml ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | bin -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @lolgab @ljmf00 @andreaTP @rtfpessoa @bmbferreira @DReigada @pedrocodacy 2 | 3 | *.yml @h314to @paulopontesm 4 | 5 | -------------------------------------------------------------------------------- /spec/utils.cr: -------------------------------------------------------------------------------- 1 | require "uuid" 2 | 3 | module Utils 4 | extend self 5 | 6 | class InTmp 7 | def initialize 8 | folder = UUID.random.to_s 9 | 10 | puts "folder #{folder}" 11 | 12 | @tmpdir = File.expand_path(folder, Dir.tempdir) 13 | 14 | FileUtils.rm_rf(@tmpdir) 15 | FileUtils.mkdir(@tmpdir) 16 | end 17 | 18 | def exec(cmd) 19 | Process.run( 20 | command: cmd, 21 | shell: true, 22 | output: STDOUT, 23 | error: STDERR, 24 | chdir: @tmpdir 25 | ).success? 26 | end 27 | 28 | def cleanup 29 | FileUtils.rm_rf(@tmpdir) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM crystallang/crystal:1.6-alpine AS builder 2 | 3 | RUN apk add --update --no-cache --force-overwrite git 4 | 5 | RUN git config --global user.email "team@codacy.com" && git config --global user.name "Codacy" 6 | 7 | RUN mkdir -p /workspace 8 | 9 | WORKDIR /workspace 10 | COPY ./ /workspace 11 | 12 | RUN make test buildStatic 13 | 14 | FROM alpine:3.15 15 | 16 | LABEL maintainer="team@codacy.com" 17 | 18 | RUN apk add --update --no-cache git 19 | 20 | COPY --from=builder /workspace/bin/git-version /bin 21 | 22 | RUN mkdir -p /repo && git config --global --add safe.directory /repo 23 | VOLUME /repo 24 | 25 | CMD ["/bin/git-version", "--folder=/repo"] 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CRYSTAL?=$(shell which crystal) 2 | CRYSTAL_FLAGS=--release 3 | CRYSTAL_STATIC_FLAGS=--static 4 | VERSION?=$(shell cat .version) 5 | 6 | all: fmt test build docker_build ## clean and produce target binary and docker image 7 | 8 | .PHONY: test 9 | test: ## runs crystal tests 10 | $(CRYSTAL) spec spec/*.cr 11 | 12 | .PHONY: fmt 13 | fmt: ## format the crystal sources 14 | $(CRYSTAL) tool format 15 | 16 | build: ## compiles from crystal sources 17 | mkdir -p bin 18 | $(CRYSTAL) build $(CRYSTAL_FLAGS) src/main.cr -o bin/git-version 19 | 20 | .PHONY: buildStatic 21 | buildStatic: ## compiles from crystal sources into static binary 22 | mkdir -p bin 23 | crystal build $(CRYSTAL_FLAGS) $(CRYSTAL_STATIC_FLAGS) src/main.cr -o bin/git-version 24 | 25 | docker_build: ## build the docker image 26 | docker build -t codacy/git-version:$(VERSION) . 27 | 28 | .PHONY: clean 29 | clean: ## clean target directories 30 | rm -rf bin 31 | 32 | .PHONY: push-docker-image 33 | push-docker-image: ## push the docker image to the registry (DOCKER_USER and DOCKER_PASS mandatory) 34 | @docker login -u $(DOCKER_USER) -p $(DOCKER_PASS) &&\ 35 | docker build -t codacy/git-version:$(VERSION) . &&\ 36 | docker push codacy/git-version:$(VERSION) 37 | 38 | .PHONY: push-latest-docker-image 39 | push-latest-docker-image: ## push the docker image with the "latest" tag to the registry (DOCKER_USER and DOCKER_PASS mandatory) 40 | @docker login -u $(DOCKER_USER) -p $(DOCKER_PASS) &&\ 41 | docker build -t codacy/git-version:latest . &&\ 42 | docker push codacy/git-version:latest 43 | 44 | .PHONY: help 45 | help: 46 | @echo "make help" 47 | @echo "\n" 48 | @grep -E '^[a-zA-Z_/%\-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 49 | @echo "\n" 50 | -------------------------------------------------------------------------------- /src/main.cr: -------------------------------------------------------------------------------- 1 | require "option_parser" 2 | 3 | require "file_utils" 4 | 5 | require "./git-version" 6 | 7 | previous_version = false 8 | dev_branch = "dev" 9 | release_branch = "master" 10 | minor_identifier = "feature:" 11 | major_identifier = "breaking:" 12 | skip_prerelease = false 13 | prefix = "" 14 | log_paths = "" 15 | 16 | folder = FileUtils.pwd 17 | 18 | OptionParser.parse do |parser| 19 | parser.banner = "Usage: git-version [arguments]" 20 | parser.on("-f FOLDER", "--folder=FOLDER", "Execute the command in the defined folder") { |f| folder = f } 21 | parser.on("-b BRANCH", "--dev-branch=BRANCH", "Specifies the development branch") { |branch| dev_branch = branch } 22 | parser.on("-r BRANCH", "--release-branch=BRANCH", "Specifies the release branch") { |branch| release_branch = branch } 23 | parser.on("--minor-identifier=IDENTIFIER", 24 | "Specifies the string or regex to identify a minor release commit with") { |identifier| minor_identifier = identifier } 25 | parser.on("--major-identifier=IDENTIFIER", 26 | "Specifies the string or regex to identify a major release commit with") { |identifier| major_identifier = identifier } 27 | parser.on("--skip-prerelease BRANCH", "Skip the prerelase part of the version") { skip_prerelease=true } 28 | parser.on("-p PREFIX", "--version-prefix=PREFIX", "Specifies a version prefix") { |p| prefix = p } 29 | parser.on("-l PATH", "--log-paths=PATH", "") { |path| log_paths = path } 30 | parser.on("--previous-version", "Returns the previous tag instead of calculating a new one") { previous_version=true } 31 | parser.on("-h", "--help", "Show this help") { puts parser } 32 | parser.invalid_option do |flag| 33 | STDERR.puts "ERROR: #{flag} is not a valid option." 34 | STDERR.puts parser 35 | exit(1) 36 | end 37 | end 38 | 39 | git = GitVersion::Git.new(dev_branch, release_branch, minor_identifier, major_identifier, folder, prefix, log_paths, skip_prerelease) 40 | 41 | if previous_version 42 | puts "#{git.get_previous_version}" 43 | else 44 | puts "#{git.get_new_version}" 45 | end 46 | -------------------------------------------------------------------------------- /.github/workflows/create_issue_on_label.yml: -------------------------------------------------------------------------------- 1 | name: Create issue on Jira when labeled with JIRA_ISSUE_LABEL 2 | 3 | on: 4 | issues: 5 | types: [labeled] 6 | 7 | jobs: 8 | jira: 9 | env: 10 | JIRA_ISSUE_LABEL: ${{ secrets.JIRA_ISSUE_LABEL }} 11 | runs-on: ubuntu-latest 12 | steps: 13 | 14 | - name: Start workflow if GitHub issue is tagged with JIRA_ISSUE_LABEL 15 | if: github.event.label.name == env.JIRA_ISSUE_LABEL 16 | run: echo "Starting workflow" 17 | 18 | - name: Jira Login 19 | if: github.event.label.name == env.JIRA_ISSUE_LABEL 20 | id: login 21 | uses: atlassian/gajira-login@v2.0.0 22 | env: 23 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 24 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 25 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 26 | 27 | - name: Jira Create issue 28 | if: github.event.label.name == env.JIRA_ISSUE_LABEL 29 | id: create_jira_issue 30 | uses: atlassian/gajira-create@v2.0.1 31 | with: 32 | project: ${{ secrets.JIRA_PROJECT }} 33 | issuetype: ${{ secrets.JIRA_ISSUE_TYPE }} 34 | summary: "[GH#${{ github.event.issue.number }}] ${{ github.event.issue.title }}" 35 | description: | 36 | ${{ github.event.issue.body }} 37 | ---- 38 | {panel} 39 | _[Github permalink |${{ github.event.issue.html_url }}]_ 40 | {panel} 41 | 42 | - name: Update Jira issue if JIRA_UPDATE_ISSUE_BODY is defined 43 | if: github.event.label.name == env.JIRA_ISSUE_LABEL && env.JIRA_UPDATE_ISSUE_BODY != '' 44 | env: 45 | JIRA_UPDATE_ISSUE_BODY: ${{ secrets.JIRA_UPDATE_ISSUE_BODY }} 46 | run: > 47 | curl 48 | -u ${{ secrets.JIRA_USER_EMAIL }}:${{ secrets.JIRA_API_TOKEN }} 49 | -X PUT 50 | -H 'Content-Type: application/json' 51 | -d '${{ env.JIRA_UPDATE_ISSUE_BODY }}' 52 | ${{ secrets.JIRA_BASE_URL }}/rest/api/2/issue/${{ steps.create_jira_issue.outputs.issue }} 53 | 54 | - name: Change Title 55 | if: github.event.label.name == env.JIRA_ISSUE_LABEL 56 | uses: actions/github-script@v2.0.0 57 | env: 58 | JIRA_ISSUE_NUMBER: ${{ steps.create_jira_issue.outputs.issue }} 59 | GITHUB_ORIGINAL_TITLE: ${{ github.event.issue.title }} 60 | with: 61 | github-token: ${{secrets.GITHUB_TOKEN}} 62 | script: | 63 | const newTitle = `[${process.env.JIRA_ISSUE_NUMBER}] ${process.env.GITHUB_ORIGINAL_TITLE}` 64 | github.issues.update({ 65 | issue_number: context.issue.number, 66 | owner: context.repo.owner, 67 | repo: context.repo.repo, 68 | title: newTitle 69 | }) 70 | 71 | - name: Add comment after sync 72 | if: github.event.label.name == env.JIRA_ISSUE_LABEL 73 | uses: actions/github-script@v2.0.0 74 | with: 75 | github-token: ${{secrets.GITHUB_TOKEN}} 76 | script: | 77 | github.issues.createComment({ 78 | issue_number: context.issue.number, 79 | owner: context.repo.owner, 80 | repo: context.repo.repo, 81 | body: 'Internal ticket created : [${{ steps.create_jira_issue.outputs.issue }}](${{ secrets.JIRA_BASE_URL }}/browse/${{ steps.create_jira_issue.outputs.issue }})' 82 | }) 83 | -------------------------------------------------------------------------------- /.github/workflows/create_issue.yml: -------------------------------------------------------------------------------- 1 | name: Create issue on Jira 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | jira: 9 | env: 10 | JIRA_CREATE_ISSUE_AUTO: ${{ secrets.JIRA_CREATE_ISSUE_AUTO }} 11 | runs-on: ubuntu-latest 12 | steps: 13 | 14 | - name: Start workflow if JIRA_CREATE_ISSUE_AUTO is enabled 15 | if: env.JIRA_CREATE_ISSUE_AUTO == 'true' 16 | run: echo "Starting workflow" 17 | 18 | - name: Jira Login 19 | if: env.JIRA_CREATE_ISSUE_AUTO == 'true' 20 | id: login 21 | uses: atlassian/gajira-login@v2.0.0 22 | env: 23 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 24 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 25 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 26 | 27 | - name: Jira Create issue 28 | if: env.JIRA_CREATE_ISSUE_AUTO == 'true' 29 | id: create_jira_issue 30 | uses: atlassian/gajira-create@v2.0.1 31 | with: 32 | project: ${{ secrets.JIRA_PROJECT }} 33 | issuetype: ${{ secrets.JIRA_ISSUE_TYPE }} 34 | summary: "[GH#${{ github.event.issue.number }}] ${{ github.event.issue.title }}" 35 | description: | 36 | ${{ github.event.issue.body }} 37 | ---- 38 | {panel} 39 | _[Github permalink |${{ github.event.issue.html_url }}]_ 40 | {panel} 41 | 42 | - name: Update Jira issue if JIRA_UPDATE_ISSUE_BODY is defined 43 | if: env.JIRA_CREATE_ISSUE_AUTO == 'true' && env.JIRA_UPDATE_ISSUE_BODY != '' 44 | env: 45 | JIRA_UPDATE_ISSUE_BODY: ${{ secrets.JIRA_UPDATE_ISSUE_BODY }} 46 | run: > 47 | curl 48 | -u ${{ secrets.JIRA_USER_EMAIL }}:${{ secrets.JIRA_API_TOKEN }} 49 | -X PUT 50 | -H 'Content-Type: application/json' 51 | -d '${{ env.JIRA_UPDATE_ISSUE_BODY }}' 52 | ${{ secrets.JIRA_BASE_URL }}/rest/api/2/issue/${{ steps.create_jira_issue.outputs.issue }} 53 | 54 | - name: Update GitHub issue 55 | if: env.JIRA_CREATE_ISSUE_AUTO == 'true' 56 | uses: actions/github-script@v2.0.0 57 | env: 58 | JIRA_ISSUE_NUMBER: ${{ steps.create_jira_issue.outputs.issue }} 59 | GITHUB_ORIGINAL_TITLE: ${{ github.event.issue.title }} 60 | JIRA_ISSUE_LABEL: ${{ secrets.JIRA_ISSUE_LABEL }} 61 | with: 62 | github-token: ${{secrets.GITHUB_TOKEN}} 63 | script: | 64 | const newTitle = `[${process.env.JIRA_ISSUE_NUMBER}] ${process.env.GITHUB_ORIGINAL_TITLE}` 65 | github.issues.update({ 66 | issue_number: context.issue.number, 67 | owner: context.repo.owner, 68 | repo: context.repo.repo, 69 | title: newTitle 70 | }) 71 | github.issues.addLabels({ 72 | issue_number: context.issue.number, 73 | owner: context.repo.owner, 74 | repo: context.repo.repo, 75 | labels: [process.env.JIRA_ISSUE_LABEL] 76 | }) 77 | 78 | 79 | - name: Add comment after sync 80 | if: env.JIRA_CREATE_ISSUE_AUTO == 'true' 81 | uses: actions/github-script@v2.0.0 82 | with: 83 | github-token: ${{secrets.GITHUB_TOKEN}} 84 | script: | 85 | github.issues.createComment({ 86 | issue_number: context.issue.number, 87 | owner: context.repo.owner, 88 | repo: context.repo.repo, 89 | body: 'Internal ticket created : [${{ steps.create_jira_issue.outputs.issue }}](${{ secrets.JIRA_BASE_URL }}/browse/${{ steps.create_jira_issue.outputs.issue }})' 90 | }) 91 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Git Version 2 | author: "Codacy" 3 | description: 'Semver versioning based on the git history and commit messages of your repository.' 4 | branding: 5 | icon: 'git-branch' 6 | color: 'gray-dark' 7 | inputs: 8 | tool-version: 9 | description: 'The version of the tool to be ran' 10 | required: true 11 | default: latest 12 | release-branch: 13 | description: 'The name of the release branch' 14 | required: true 15 | default: master 16 | dev-branch: 17 | description: 'The name of the dev branch' 18 | required: true 19 | default: dev 20 | minor-identifier: 21 | description: 'The string or regex to identify a minor release commit' 22 | required: true 23 | default: 'feature:' 24 | major-identifier: 25 | description: 'The string or regex to identify a major release commit' 26 | required: true 27 | default: 'breaking:' 28 | skip-prerelease: 29 | description: 'Skip prerelease part of the version. When true, release-branch and dev-branch are effectively ignored' 30 | required: true 31 | default: 'false' 32 | prefix: 33 | description: 'The prefix to use in the version' 34 | required: false 35 | log-paths: 36 | description: 'The paths to be used to calculate changes (comma-separated)' 37 | required: false 38 | default: ./ 39 | outputs: 40 | version: 41 | description: 'The value of the new pre-calculated tag' 42 | value: ${{ steps.version.outputs.version }} 43 | non-prefixed-version: 44 | description: 'The value of the new pre-calculated tag without the prefix' 45 | value: ${{ steps.version.outputs.non-prefixed-version }} 46 | previous-version: 47 | description: 'Contains the value of previous tag, before calculating a new one' 48 | value: ${{ steps.previous-version.outputs.previous-version }} 49 | runs: 50 | using: "composite" 51 | steps: 52 | - shell: bash 53 | run: | 54 | set -eo pipefail 55 | if [ "${{ inputs.tool-version }}" = "latest" ]; then 56 | download_url="$(curl -Ls https://api.github.com/repos/codacy/git-version/releases/latest | jq -r .assets[0].browser_download_url)" 57 | else 58 | download_url="https://github.com/codacy/git-version/releases/download/${{ inputs.tool-version }}/git-version" 59 | fi 60 | curl -Ls "$download_url" > /usr/local/bin/git-version 61 | chmod +x /usr/local/bin/git-version 62 | - id: previous-version 63 | shell: bash 64 | run: | 65 | set -eo pipefail 66 | 67 | export PREVIOUS_VERSION=$(git-version \ 68 | --previous-version \ 69 | --release-branch "${{ inputs.release-branch }}" \ 70 | --dev-branch "${{ inputs.dev-branch }}" \ 71 | --minor-identifier="${{ inputs.minor-identifier }}" \ 72 | --major-identifier="${{ inputs.major-identifier }}" \ 73 | ${{ inputs.skip-prerelease == 'true' && '--skip-prerelease' || '' }} \ 74 | --version-prefix "${{ inputs.prefix }}") 75 | 76 | echo "previous-version=$PREVIOUS_VERSION" >> $GITHUB_OUTPUT 77 | echo "Previous Version: $PREVIOUS_VERSION" 78 | - id: version 79 | shell: bash 80 | run: | 81 | set -eo pipefail 82 | 83 | export VERSION=$(git-version \ 84 | --release-branch "${{ inputs.release-branch }}" \ 85 | --dev-branch "${{ inputs.dev-branch }}" \ 86 | --minor-identifier="${{ inputs.minor-identifier }}" \ 87 | --major-identifier="${{ inputs.major-identifier }}" \ 88 | --log-paths="${{ inputs.log-paths }}" \ 89 | ${{ inputs.skip-prerelease == 'true' && '--skip-prerelease' || '' }} \ 90 | --version-prefix "${{ inputs.prefix }}") 91 | 92 | prefix="${{ inputs.prefix }}" 93 | non_prefixed_version=${VERSION#"$prefix"} 94 | echo "non-prefixed-version=$non_prefixed_version" >> $GITHUB_OUTPUT 95 | echo "version=$VERSION" >> $GITHUB_OUTPUT 96 | echo "New Version: $VERSION" 97 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | codacy: codacy/base@12.2.0 5 | 6 | # Re-usable blocks to reduce boilerplate in job definitions. 7 | references: 8 | default_machine_job: &default_machine_job 9 | machine: true 10 | working_directory: ~/workdir 11 | 12 | jobs: 13 | compile: 14 | <<: *default_machine_job 15 | steps: 16 | - attach_workspace: 17 | at: ~/workdir 18 | - run: 19 | name: Get version 20 | command: | 21 | curl -fsSL https://crystal-lang.org/install.sh | sudo bash 22 | sudo apt-get update 23 | sudo apt-get install crystal 24 | make clean test build 25 | ./bin/git-version > .version 26 | cat .version 27 | - persist_to_workspace: 28 | root: ~/workdir 29 | paths: 30 | - "*" 31 | - store_artifacts: 32 | path: bin 33 | 34 | build_docker: 35 | <<: *default_machine_job 36 | steps: 37 | - attach_workspace: 38 | at: ~/workdir 39 | - run: 40 | name: Build Docker image 41 | command: make docker_build 42 | 43 | build_static: 44 | <<: *default_machine_job 45 | steps: 46 | - attach_workspace: 47 | at: ~/workdir 48 | - run: 49 | name: Build Docker and extract from image 50 | command: | 51 | make docker_build 52 | docker run --entrypoint cat codacy/git-version:$(cat .version) /bin/git-version > bin/git-version 53 | - persist_to_workspace: 54 | root: ~/workdir 55 | paths: 56 | - "*" 57 | - store_artifacts: 58 | path: bin 59 | 60 | publish_versioned: 61 | <<: *default_machine_job 62 | steps: 63 | - attach_workspace: 64 | at: ~/workdir 65 | - run: 66 | name: Publish images to Docker Hub 67 | command: make push-docker-image 68 | 69 | publish_latest: 70 | <<: *default_machine_job 71 | steps: 72 | - attach_workspace: 73 | at: ~/workdir 74 | - run: 75 | name: Publish latest image to Docker Hub 76 | command: make push-latest-docker-image 77 | 78 | workflows: 79 | version: 2 80 | publish: 81 | jobs: 82 | - codacy/checkout_and_version: 83 | filters: 84 | branches: 85 | ignore: master 86 | - compile: 87 | requires: 88 | - codacy/checkout_and_version 89 | - build_docker: 90 | requires: 91 | - compile 92 | - manual_approval: 93 | type: approval 94 | requires: 95 | - build_docker 96 | - publish_versioned: 97 | requires: 98 | - manual_approval 99 | context: CodacyDocker 100 | - codacy/tag_version: 101 | context: CodacyAWS 102 | requires: 103 | - publish_versioned 104 | 105 | publish_master: 106 | jobs: 107 | - codacy/checkout_and_version: 108 | filters: 109 | branches: 110 | only: master 111 | - compile: 112 | requires: 113 | - codacy/checkout_and_version 114 | - build_static: 115 | requires: 116 | - compile 117 | - codacy/publish_ghr: 118 | path: ~/workdir/bin/ 119 | requires: 120 | - build_static 121 | context: CodacyAWS 122 | - build_docker: 123 | requires: 124 | - compile 125 | - publish_versioned: 126 | requires: 127 | - build_docker 128 | context: CodacyDocker 129 | - codacy/publish_s3: 130 | path: bin/git-version 131 | files: bin/git-version 132 | context: CodacyAWS 133 | requires: 134 | - build_static 135 | - publish_latest: 136 | requires: 137 | - publish_versioned 138 | context: CodacyDocker 139 | -------------------------------------------------------------------------------- /.github/workflows/comment_issue.yml: -------------------------------------------------------------------------------- 1 | name: Comment issue on Jira 2 | 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | jobs: 8 | jira: 9 | env: 10 | JIRA_CREATE_COMMENT_AUTO: ${{ secrets.JIRA_CREATE_COMMENT_AUTO }} 11 | runs-on: ubuntu-latest 12 | steps: 13 | 14 | - name: Start workflow if JIRA_CREATE_COMMENT_AUTO is enabled 15 | if: env.JIRA_CREATE_COMMENT_AUTO == 'true' 16 | run: echo "Starting workflow" 17 | 18 | - name: Check GitHub Issue type 19 | if: env.JIRA_CREATE_COMMENT_AUTO == 'true' 20 | id: github_issue_type 21 | uses: actions/github-script@v2.0.0 22 | with: 23 | result-encoding: string 24 | script: | 25 | // An Issue can be a pull request, you can identify pull requests by the pull_request key 26 | const pullRequest = ${{ toJson(github.event.issue.pull_request) }} 27 | if(pullRequest) { 28 | return "pull-request" 29 | } else { 30 | return "issue" 31 | } 32 | 33 | - name: Check if GitHub Issue has JIRA_ISSUE_LABEL 34 | if: env.JIRA_CREATE_COMMENT_AUTO == 'true' 35 | id: github_issue_has_jira_issue_label 36 | uses: actions/github-script@v2.0.0 37 | env: 38 | JIRA_ISSUE_LABEL: ${{ secrets.JIRA_ISSUE_LABEL }} 39 | with: 40 | result-encoding: string 41 | script: | 42 | const labels = ${{ toJson(github.event.issue.labels) }} 43 | if(labels.find(label => label.name == process.env.JIRA_ISSUE_LABEL)) { 44 | return "true" 45 | } else { 46 | return "false" 47 | } 48 | 49 | - name: Continue workflow only for Issues (not Pull Requests) tagged with JIRA_ISSUE_LABEL 50 | if: env.JIRA_CREATE_COMMENT_AUTO == 'true' && env.GITHUB_ISSUE_TYPE == 'issue' && env.GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL == 'true' 51 | env: 52 | GITHUB_ISSUE_TYPE: ${{ steps.github_issue_type.outputs.result }} 53 | GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL: ${{ steps.github_issue_has_jira_issue_label.outputs.result }} 54 | run: echo "GitHub Issue is tracked on Jira, eligilbe to be commented" 55 | 56 | - name: Jira Login 57 | if: env.JIRA_CREATE_COMMENT_AUTO == 'true' && env.GITHUB_ISSUE_TYPE == 'issue' && env.GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL == 'true' 58 | id: login 59 | uses: atlassian/gajira-login@v2.0.0 60 | env: 61 | GITHUB_ISSUE_TYPE: ${{ steps.github_issue_type.outputs.result }} 62 | GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL: ${{ steps.github_issue_has_jira_issue_label.outputs.result }} 63 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 64 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 65 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 66 | 67 | - name: Extract Jira number 68 | if: env.JIRA_CREATE_COMMENT_AUTO == 'true' && env.GITHUB_ISSUE_TYPE == 'issue' && env.GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL == 'true' 69 | id: extract_jira_number 70 | uses: actions/github-script@v2.0.0 71 | env: 72 | GITHUB_ISSUE_TYPE: ${{ steps.github_issue_type.outputs.result }} 73 | GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL: ${{ steps.github_issue_has_jira_issue_label.outputs.result }} 74 | JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }} 75 | GITHUB_TITLE: ${{ github.event.issue.title }} 76 | with: 77 | script: | 78 | const jiraTaskRegex = new RegExp(`\\\[(${process.env.JIRA_PROJECT}-[0-9]+?)\\\]`) 79 | return process.env.GITHUB_TITLE.match(jiraTaskRegex)[1] 80 | result-encoding: string 81 | 82 | - name: Jira Add comment on issue 83 | if: env.JIRA_CREATE_COMMENT_AUTO == 'true' && env.GITHUB_ISSUE_TYPE == 'issue' && env.GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL == 'true' 84 | id: add_comment_jira_issue 85 | uses: atlassian/gajira-comment@v2.0.2 86 | env: 87 | GITHUB_ISSUE_TYPE: ${{ steps.github_issue_type.outputs.result }} 88 | GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL: ${{ steps.github_issue_has_jira_issue_label.outputs.result }} 89 | with: 90 | issue: ${{ steps.extract_jira_number.outputs.result }} 91 | comment: | 92 | GitHub Comment : ${{ github.event.comment.user.login }} 93 | {quote}${{ github.event.comment.body }}{quote} 94 | ---- 95 | {panel} 96 | _[Github permalink |${{ github.event.comment.html_url }}]_ 97 | {panel} 98 | -------------------------------------------------------------------------------- /src/git-version.cr: -------------------------------------------------------------------------------- 1 | require "file_utils" 2 | 3 | require "semantic_version" 4 | 5 | module GitVersion 6 | extend self 7 | 8 | BASE_VERSION_STRING = "0.0.0" 9 | BASE_VERSION = SemanticVersion.parse(BASE_VERSION_STRING) 10 | 11 | DEV_BRANCH_SUFFIX = "SNAPSHOT" 12 | 13 | class Git 14 | def initialize(@dev_branch : String, @release_branch : String, @minor_identifier : String, @major_identifier : String, 15 | @folder = FileUtils.pwd, @prefix : String = "", @log_paths : String = "", @skip_prerelease : Bool = false) 16 | @major_id_is_regex = false 17 | @minor_id_is_regex = false 18 | if match = /\/(.*)\//.match(@major_identifier) 19 | @major_identifier = match[1] 20 | @major_id_is_regex = true 21 | end 22 | if match = /\/(.*)\//.match(@minor_identifier) 23 | @minor_identifier = match[1] 24 | @minor_id_is_regex = true 25 | end 26 | # 27 | end 28 | 29 | private def add_prefix(version : String) : String 30 | return "#{@prefix}#{version}" 31 | end 32 | 33 | private def strip_prefix(version : String) : String | Nil 34 | version.lchop?(@prefix) 35 | end 36 | 37 | private def exec(cmd) 38 | strout = IO::Memory.new 39 | 40 | if !Process.run( 41 | command: cmd, 42 | shell: true, 43 | output: strout, 44 | error: Process::Redirect::Close, 45 | chdir: @folder 46 | ).success? 47 | raise "[ERROR] Command #{cmd} failed." 48 | end 49 | 50 | return strout.to_s.split('\n', remove_empty: true) 51 | end 52 | 53 | def dev_branch 54 | return @dev_branch 55 | end 56 | 57 | def log_paths_filter 58 | @log_paths.empty? ? "" : "-- #{@log_paths}" 59 | end 60 | 61 | def release_branch 62 | return @release_branch 63 | end 64 | 65 | def tags_by_branch(branch) 66 | return exec "git tag --merged #{branch}" 67 | end 68 | 69 | def current_branch_or_tag 70 | return (exec "git symbolic-ref --short HEAD")[0] 71 | rescue 72 | return (exec "git describe --tags")[0] 73 | end 74 | 75 | def current_commit_hash : String 76 | cmd = "git rev-parse --verify HEAD --short" 77 | sha = (exec cmd)[0].strip 78 | return "sha." + sha 79 | end 80 | 81 | def current_commit_hash_without_prefix : String 82 | cmd = "git rev-parse --verify HEAD --short" 83 | return (exec cmd)[0].strip 84 | end 85 | 86 | def commits_distance(tag : String | Nil) 87 | if tag.nil? 88 | return (exec "git rev-list --count HEAD")[0] 89 | else 90 | return (exec "git rev-list --count HEAD ^#{tag}")[0] 91 | end 92 | rescue 93 | return 0 94 | end 95 | 96 | def get_commits_since(tag : String | Nil) 97 | if !tag.nil? && (exec "git tag -l #{tag}").any? 98 | last_commit = (exec "git show-ref -s #{tag}")[0] 99 | return (exec "git log --pretty=%B #{last_commit}..HEAD #{log_paths_filter}") 100 | else 101 | return (exec "git log --pretty=%B") 102 | end 103 | rescue 104 | return [] of String 105 | end 106 | 107 | def get_previous_tag_and_version : Tuple(String | Nil, SemanticVersion) 108 | cb = current_branch_or_tag 109 | 110 | branch_tags = tags_by_branch(cb) 111 | 112 | previous_version = BASE_VERSION 113 | previous_tag = nil 114 | 115 | branch_tags.each do |tag| 116 | begin 117 | tag_without_prefix = strip_prefix(tag) 118 | if tag_without_prefix.nil? 119 | next 120 | end 121 | current_version = SemanticVersion.parse(tag_without_prefix) 122 | if !current_version.prerelease.identifiers.empty? 123 | next 124 | elsif (previous_version < current_version) 125 | previous_version = current_version 126 | previous_tag = tag 127 | end 128 | rescue 129 | # 130 | end 131 | end 132 | return {previous_tag, previous_version} 133 | end 134 | 135 | def get_previous_version : String 136 | lt, lv = get_previous_tag_and_version 137 | return lt ? lt : add_prefix(lv.to_s) 138 | end 139 | 140 | def get_new_version 141 | previous_tag, previous_version = get_previous_tag_and_version 142 | 143 | previous_version = 144 | SemanticVersion.new( 145 | previous_version.major, 146 | previous_version.minor, 147 | previous_version.patch + 1, 148 | nil, 149 | nil, 150 | ) 151 | 152 | major = false 153 | get_commits_since(previous_tag).each do |c| 154 | commit = c.downcase 155 | match = /^#{@major_identifier}/.match(commit) 156 | if match 157 | previous_version = 158 | SemanticVersion.new( 159 | previous_version.major + 1, 160 | 0, 161 | 0, 162 | previous_version.prerelease, 163 | previous_version.build, 164 | ) 165 | major = true 166 | break 167 | end 168 | end 169 | 170 | if !major 171 | get_commits_since(previous_tag).each do |c| 172 | commit = c.downcase 173 | match = if @minor_id_is_regex 174 | /#{@minor_identifier}/.match(commit) 175 | else 176 | commit.includes?(@minor_identifier) 177 | end 178 | if match 179 | previous_version = 180 | SemanticVersion.new( 181 | previous_version.major, 182 | previous_version.minor + 1, 183 | 0, 184 | previous_version.prerelease, 185 | previous_version.build, 186 | ) 187 | break 188 | end 189 | end 190 | end 191 | 192 | cb = current_branch_or_tag 193 | 194 | if ! @skip_prerelease 195 | cb = current_branch_or_tag 196 | 197 | if cb == @release_branch 198 | # 199 | elsif cb == @dev_branch 200 | prerelease = [DEV_BRANCH_SUFFIX, commits_distance(previous_tag), current_commit_hash()] of String | Int32 201 | previous_version = 202 | SemanticVersion.new( 203 | previous_version.major, 204 | previous_version.minor, 205 | previous_version.patch, 206 | SemanticVersion::Prerelease.new(prerelease), 207 | nil 208 | ) 209 | else 210 | branch_sanitized_name = cb.downcase.gsub(/[^a-zA-Z0-9]/, "")[0,30] 211 | prerelease = [branch_sanitized_name, commits_distance(previous_tag), current_commit_hash()] of String | Int32 212 | previous_version = 213 | SemanticVersion.new( 214 | previous_version.major, 215 | previous_version.minor, 216 | previous_version.patch, 217 | SemanticVersion::Prerelease.new(prerelease), 218 | nil 219 | ) 220 | end 221 | end 222 | return add_prefix(previous_version.to_s) 223 | end 224 | 225 | end 226 | end 227 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-version 2 | 3 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/c811f6b557ee4e44ad373084015ba0b3)](https://www.codacy.com/gh/codacy/git-version?utm_source=github.com&utm_medium=referral&utm_content=codacy/git-version&utm_campaign=Badge_Grade) 4 | [![CircleCI](https://circleci.com/gh/codacy/git-version.svg?style=svg)](https://circleci.com/gh/codacy/git-version) 5 | [![](https://images.microbadger.com/badges/version/codacy/git-version.svg)](https://microbadger.com/images/codacy/git-version "Get your own version badge on microbadger.com") 6 | 7 | The goal of this tool is to have a simple versioning system that we can use to track the different releases. The tool prints the current version (e.g. to be used for tagging) depending on the git history and commit messages. 8 | 9 | The versioning scheme is assumed to be __Semver__ based. 10 | 11 | ## Usage 12 | 13 | ```yaml 14 | # .github/workflows/version.yml 15 | name: Git Version 16 | 17 | on: 18 | push: 19 | branches: 20 | - master 21 | 22 | jobs: 23 | lint: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout Code 27 | uses: actions/checkout@v3 28 | with: 29 | ref: ${{ github.head_ref }} # checkout the correct branch name 30 | fetch-depth: 0 # fetch the whole repo history 31 | 32 | - name: Git Version 33 | id: version 34 | uses: codacy/git-version@2.7.1 35 | 36 | - name: Use the version 37 | run: | 38 | echo ${{ steps.version.outputs.version }} 39 | - name: Use the previous version 40 | run: | 41 | echo ${{ steps.version.outputs.previous-version }} 42 | ``` 43 | 44 | ### Mono-Repo 45 | 46 | You can use git-version to version different modules in a mono-repo structure. 47 | This can be achieved by using different `prefixes` and `log-path` filters for 48 | different modules. 49 | 50 | Assuming the following directory structure, we can use git-version to generate 51 | version with prefix `module1-x.x.x` for changes in the `module1/` directory 52 | and `module2-x.x.x` for changes in the `module2/` directory. 53 | 54 | ```sh 55 | . 56 | ├── Dockerfile 57 | ├── Makefile 58 | ├── README.md 59 | ├── module1 60 | │ ├── Dockerfile 61 | │ └── src/ 62 | └── module2 63 | ├── Dockerfile 64 | └── src/ 65 | ``` 66 | 67 | With github actions you can create different workflows that are triggered 68 | when changes happen on different directories. 69 | 70 | ```yaml 71 | # .github/workflows/module1.yml 72 | name: Version Module 1 73 | 74 | on: 75 | pull_request: 76 | paths: 77 | - .github/workflows/module1.yml 78 | - module1/** 79 | push: 80 | paths: 81 | - .github/workflows/module1.yml 82 | - module1/** 83 | branches: 84 | - master 85 | 86 | jobs: 87 | lint: 88 | runs-on: ubuntu-latest 89 | steps: 90 | - name: Checkout Code 91 | uses: actions/checkout@v3 92 | with: 93 | ref: ${{ github.head_ref }} # checkout the correct branch name 94 | fetch-depth: 0 # fetch the whole repo history 95 | 96 | - name: Git Version 97 | uses: codacy/git-version@2.5.4 98 | with: 99 | prefix: module1- 100 | log-path: module1/ 101 | ``` 102 | 103 | ```yaml 104 | # .github/workflows/module2.yml 105 | name: Version Module 2 106 | 107 | on: 108 | pull_request: 109 | paths: 110 | - .github/workflows/module2.yml 111 | - module2/** 112 | push: 113 | paths: 114 | - .github/workflows/module2.yml 115 | - module2/** 116 | branches: 117 | - master 118 | 119 | jobs: 120 | lint: 121 | runs-on: ubuntu-latest 122 | steps: 123 | - name: Checkout Code 124 | uses: actions/checkout@v3 125 | with: 126 | ref: ${{ github.head_ref }} # checkout the correct branch name 127 | fetch-depth: 0 # fetch the whole repo history 128 | 129 | - name: Git Version 130 | uses: codacy/git-version@2.5.4 131 | with: 132 | prefix: module2- 133 | log-path: module2/ 134 | ``` 135 | 136 | ## Versioning Model 137 | 138 | Creates a version with the format `MAJOR.MINOR.PATCH` 139 | 140 | _To use this you need to be in the working dir of a git project:_ 141 | ``` 142 | $ ./git-version 143 | 1.0.0 144 | ``` 145 | 146 | Versions are incremented since the last tag. The patch version is incremented by default, unless there is at least one commit since the last tag, containing a minor or major identifier (defaults to `feature:` or `breaking:`) in the message. 147 | 148 | On branches other than the master/main and development branch (default to `master` and `dev`) the version is a variation of the latest common tag with the master/main branch, and has the following format: 149 | 150 | `{MAJOR}.{MINOR}.{PATCH}-{sanitized-branch-name}.{commits-distance}.{hash}` 151 | 152 | On the development branch the format is the following: 153 | 154 | `{MAJOR}.{MINOR}.{PATCH}-SNAPSHOT.{hash}` 155 | 156 | _Example:_ 157 | ``` 158 | ---A---B---C <= Master (tag: 1.0.1) L <= Master (git-version: 1.0.2) 159 | \ / 160 | D---E---F---G---H---I---J---K <= Foo (git-version: 1.0.2-foo.8.5e30d83) 161 | ``` 162 | 163 | _Example2 (with dev branch):_ 164 | ``` 165 | ---A---B---C <= Master (tag: 1.0.1) L <= Master (git-version: 1.0.2) 166 | \ / <= Fast-forward merges to master (same commit id) 167 | C L <= Dev (git-version: 1.0.2-SNAPSHOT.5e30d83) 168 | \ / 169 | E---F---G---H---I---J---K <= Foo (new_version: 1.0.1-foo.7.5e30d83) 170 | ``` 171 | 172 | _Example3 (with breaking message):_ 173 | ``` 174 | ---A---B---C <= Master (tag: 1.0.1) L <= Master (git-version: 2.0.0) 175 | \ / 176 | D---E---F---G---H---I---J---K <= Foo (git-version: 2.0.0-foo.8.5e30d83) 177 | \\ 178 | message: "breaking: removed api parameter" 179 | ``` 180 | 181 | ### Configuration 182 | 183 | You can configure the action with various inputs, a list of which has been provided below: 184 | 185 | | Name | Description | Default Value | 186 | |------------------|-------------------------------------------------------------------------------------------------|---------------| 187 | | tool-version | The version of the tool to run | latest | 188 | | release-branch | The name of the master/main branch | master | 189 | | dev-branch | The name of the development branch | dev | 190 | | minor-identifier | The string used to identify a minor release (wrap with '/' to match using a regular expression) | feature: | 191 | | major-identifier | The string used to identify a major release (wrap with '/' to match using a regular expression) | breaking: | 192 | | skip-prerelease | When true, release-branch and dev-branch are effectively ignored | false | 193 | | prefix | The prefix used for the version name | | 194 | | log-paths | The paths used to calculate changes (comma-separated) | | 195 | 196 | ## Requirements 197 | 198 | To use this tool you will need to install a few dependencies: 199 | 200 | Ubuntu: 201 | ``` 202 | sudo apt-get install \ 203 | libevent-dev \ 204 | git 205 | ``` 206 | 207 | Fedora: 208 | ``` 209 | sudo dnf -y install \ 210 | libevent-devel \ 211 | git 212 | ``` 213 | 214 | Alpine: 215 | ``` 216 | apk add --update --no-cache --force-overwrite \ 217 | gc-dev pcre-dev libevent-dev \ 218 | git 219 | ``` 220 | 221 | OsX: 222 | ``` 223 | brew install \ 224 | libevent \ 225 | git 226 | ``` 227 | 228 | 229 | ## CircleCI 230 | 231 | Use this image directly on CircleCI for simple steps 232 | 233 | ``` 234 | version: 2 235 | jobs: 236 | build: 237 | machine: true 238 | working_directory: /app 239 | steps: 240 | - checkout 241 | - run: 242 | name: get new version 243 | command: | 244 | NEW_VERSION=$(docker run --rm -v $(pwd):/repo codacy/git-version) 245 | echo $NEW_VERSION 246 | ``` 247 | 248 | ## Build and Publish 249 | 250 | The pipeline in `circleci` can deploy this for you when the code is pushed to the remote. 251 | 252 | To compile locally you need to install [crystal](https://crystal-lang.org/install/) and possibly [all required libraries](https://github.com/crystal-lang/crystal/wiki/All-required-libraries) 253 | 254 | You can also run everything locally using the makefile. 255 | 256 | To get the list of available commands: 257 | ``` 258 | $ make help 259 | ``` 260 | 261 | ## Credits 262 | 263 | Great inspiration for this tool has been taken from: [GitVersion](https://github.com/GitTools/GitVersion) 264 | 265 | ## What is Codacy 266 | 267 | [Codacy](https://www.codacy.com/) is an Automated Code Review Tool that monitors your technical debt, helps you improve your code quality, teaches best practices to your developers, and helps you save time in Code Reviews. 268 | 269 | ### Among Codacy’s features 270 | 271 | - Identify new Static Analysis issues 272 | - Commit and Pull Request Analysis with GitHub, BitBucket/Stash, GitLab (and also direct git repositories) 273 | - Auto-comments on Commits and Pull Requests 274 | - Integrations with Slack, HipChat, Jira, YouTrack 275 | - Track issues in Code Style, Security, Error Proneness, Performance, Unused Code and other categories 276 | 277 | Codacy also helps keep track of Code Coverage, Code Duplication, and Code Complexity. 278 | 279 | Codacy supports PHP, Python, Ruby, Java, JavaScript, and Scala, among others. 280 | 281 | ## Free for Open Source 282 | 283 | Codacy is free for Open Source projects. 284 | 285 | ## License 286 | 287 | git-version is available under the Apache 2 license. See the [LICENSE](./LICENSE) file for more info. 288 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2023 @ Codacy 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. -------------------------------------------------------------------------------- /spec/git-version-spec.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "file_utils" 3 | require "./utils" 4 | 5 | require "../src/git-version" 6 | 7 | include Utils 8 | describe GitVersion do 9 | it "should match hash with hash without prefix" do 10 | tmp = InTmp.new 11 | begin 12 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 13 | 14 | tmp.exec %(git init) 15 | tmp.exec %(git checkout -b #{git.release_branch}) 16 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 17 | tmp.exec %(git tag "1.0.0") 18 | 19 | version = git.get_new_version 20 | 21 | version.should eq("1.0.1") 22 | 23 | tmp.exec %(git checkout -b dev) 24 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "2") 25 | 26 | tag_on_master = git.tags_by_branch("#{git.release_branch}") 27 | 28 | tag_on_master.should eq(["1.0.0"]) 29 | 30 | hash = git.current_commit_hash 31 | hashWithoutPrefix = git.current_commit_hash_without_prefix 32 | 33 | hash.should eq("sha.#{hashWithoutPrefix}") 34 | end 35 | end 36 | it "should get the correct version in master and dev branch" do 37 | tmp = InTmp.new 38 | 39 | begin 40 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 41 | 42 | tmp.exec %(git init) 43 | tmp.exec %(git checkout -b #{git.release_branch}) 44 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 45 | tmp.exec %(git tag "1.0.0") 46 | 47 | version = git.get_new_version 48 | 49 | version.should eq("1.0.1") 50 | 51 | tmp.exec %(git checkout -b dev) 52 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "2") 53 | 54 | tag_on_master = git.tags_by_branch("#{git.release_branch}") 55 | 56 | tag_on_master.should eq(["1.0.0"]) 57 | 58 | current_branch = git.current_branch_or_tag 59 | 60 | current_branch.should eq("#{git.dev_branch}") 61 | 62 | hash = git.current_commit_hash 63 | 64 | version = git.get_new_version 65 | 66 | version.should eq("1.0.1-SNAPSHOT.1.#{hash}") 67 | 68 | tmp.exec %(git checkout -b feature-branch) 69 | tmp.exec %(touch file2.txt) 70 | tmp.exec %(git add file2.txt) 71 | tmp.exec %(git commit --no-gpg-sign -m "new file2.txt") 72 | ensure 73 | tmp.cleanup 74 | end 75 | end 76 | 77 | it "should get the correct version feature branch" do 78 | tmp = InTmp.new 79 | 80 | begin 81 | tmp.exec %(git init) 82 | tmp.exec %(git checkout -b master) 83 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 84 | tmp.exec %(git tag "1.0.0") 85 | 86 | tmp.exec %(git checkout -b my-fancy.branch) 87 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "2") 88 | 89 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 90 | 91 | hash = git.current_commit_hash 92 | 93 | version = git.get_new_version 94 | 95 | version.should eq("1.0.1-myfancybranch.1.#{hash}") 96 | ensure 97 | tmp.cleanup 98 | end 99 | end 100 | 101 | it "should skip prerelease component in the version number when configured" do 102 | tmp = InTmp.new 103 | 104 | begin 105 | tmp.exec %(git init) 106 | tmp.exec %(git checkout -b master) 107 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 108 | tmp.exec %(git tag "1.0.0") 109 | 110 | tmp.exec %(git checkout -b my-test.branch) 111 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "2") 112 | 113 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "", "", true) 114 | 115 | version = git.get_new_version 116 | 117 | version.should eq("1.0.1") 118 | ensure 119 | tmp.cleanup 120 | end 121 | end 122 | 123 | it "should properly bump the version" do 124 | tmp = InTmp.new 125 | 126 | begin 127 | tmp.exec %(git init) 128 | tmp.exec %(git checkout -b master) 129 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 130 | tmp.exec %(git tag "1.0.0") 131 | 132 | tmp.exec %(git checkout -b dev) 133 | 134 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 135 | 136 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: XYZ") 137 | 138 | hash = git.current_commit_hash 139 | 140 | version = git.get_new_version 141 | 142 | version.should eq("2.0.0-SNAPSHOT.1.#{hash}") 143 | 144 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: XYZ") 145 | 146 | hash = git.current_commit_hash 147 | 148 | version = git.get_new_version 149 | 150 | version.should eq("2.0.0-SNAPSHOT.2.#{hash}") 151 | 152 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "feature: XYZ") 153 | 154 | hash = git.current_commit_hash 155 | 156 | version = git.get_new_version 157 | 158 | version.should eq("2.0.0-SNAPSHOT.3.#{hash}") 159 | ensure 160 | tmp.cleanup 161 | end 162 | end 163 | 164 | it "bump on master after merging in various ways" do 165 | tmp = InTmp.new 166 | 167 | begin 168 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 169 | 170 | tmp.exec %(git init) 171 | tmp.exec %(git checkout -b master) 172 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 173 | tmp.exec %(git tag "1.0.0") 174 | 175 | tmp.exec %(git checkout -b my-fancy.branch) 176 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "2") 177 | 178 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: XYZ") 179 | 180 | tmp.exec %(git checkout master) 181 | 182 | version = git.get_new_version 183 | 184 | version.should eq("1.0.1") 185 | 186 | tmp.exec %(git merge my-fancy.branch) 187 | 188 | version = git.get_new_version 189 | 190 | version.should eq("2.0.0") 191 | 192 | tmp.exec %(git tag "2.0.0") 193 | 194 | tmp.exec %(git checkout -b my-fancy.branch2) 195 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: ABC") 196 | tmp.exec %(git checkout master) 197 | 198 | version = git.get_new_version 199 | 200 | version.should eq("2.0.1") 201 | 202 | tmp.exec %(git merge --ff-only my-fancy.branch2) 203 | 204 | version = git.get_new_version 205 | 206 | version.should eq("3.0.0") 207 | 208 | tmp.exec %(git tag "3.0.0") 209 | 210 | tmp.exec %(git checkout -b my-fancy.branch3) 211 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "feature: 123") 212 | tmp.exec %(git checkout master) 213 | 214 | version = git.get_new_version 215 | 216 | version.should eq("3.0.1") 217 | 218 | tmp.exec %(git merge --no-gpg-sign --no-ff my-fancy.branch3) 219 | 220 | version = git.get_new_version 221 | 222 | version.should eq("3.1.0") 223 | ensure 224 | tmp.cleanup 225 | end 226 | end 227 | 228 | it "correct version on feature after second commit" do 229 | tmp = InTmp.new 230 | 231 | begin 232 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 233 | 234 | tmp.exec %(git init) 235 | tmp.exec %(git checkout -b master) 236 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 237 | tmp.exec %(git tag "1.0.0") 238 | 239 | tmp.exec %(# Checkout to dev) 240 | tmp.exec %(git checkout -b dev) 241 | 242 | # Checkout to FT-1111 from dev and add a commit) 243 | tmp.exec %(git checkout -b FT-1111) 244 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "3") 245 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "4") 246 | 247 | hash = git.current_commit_hash 248 | 249 | version = git.get_new_version 250 | 251 | version.should eq("1.0.1-ft1111.2.#{hash}") 252 | ensure 253 | tmp.cleanup 254 | end 255 | end 256 | 257 | it "should retrieve correct first version on master" do 258 | tmp = InTmp.new 259 | 260 | begin 261 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 262 | 263 | tmp.exec %(git init) 264 | tmp.exec %(git checkout -b master) 265 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 266 | 267 | version = git.get_new_version 268 | 269 | version.should eq("0.0.1") 270 | ensure 271 | tmp.cleanup 272 | end 273 | end 274 | 275 | it "version properly after 5th commit" do 276 | tmp = InTmp.new 277 | 278 | begin 279 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 280 | 281 | tmp.exec %(git init) 282 | tmp.exec %(git checkout -b master) 283 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 284 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "2") 285 | tmp.exec %(git tag "1.1.0") 286 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "3") 287 | tmp.exec %(git tag "1.2.0") 288 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "4") 289 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "5") 290 | 291 | version = git.get_new_version 292 | 293 | version.should eq("1.2.1") 294 | ensure 295 | tmp.cleanup 296 | end 297 | end 298 | 299 | it "version properly with concurrent features" do 300 | tmp = InTmp.new 301 | 302 | begin 303 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 304 | 305 | tmp.exec %(git init) 306 | tmp.exec %(git checkout -b master) 307 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 308 | tmp.exec %(git tag "1.0.0") 309 | tmp.exec %(git checkout -b feature1) 310 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "feature: 2") 311 | hash = git.current_commit_hash 312 | version = git.get_new_version 313 | version.should eq("1.1.0-feature1.1.#{hash}") 314 | 315 | tmp.exec %(git checkout master) 316 | tmp.exec %(git checkout -b feature2) 317 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 3") 318 | hash = git.current_commit_hash 319 | version = git.get_new_version 320 | version.should eq("2.0.0-feature2.1.#{hash}") 321 | 322 | tmp.exec %(git checkout master) 323 | tmp.exec %(git merge feature2) 324 | version = git.get_new_version 325 | version.should eq("2.0.0") 326 | tmp.exec %(git tag "2.0.0") 327 | 328 | tmp.exec %(git checkout -b feature3) 329 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "4") 330 | hash = git.current_commit_hash 331 | version = git.get_new_version 332 | version.should eq("2.0.1-feature3.1.#{hash}") 333 | 334 | tmp.exec %(git checkout master) 335 | tmp.exec %(git merge --no-gpg-sign feature1) 336 | version = git.get_new_version 337 | version.should eq("2.1.0") 338 | tmp.exec %(git tag "2.1.0") 339 | 340 | tmp.exec %(git merge --no-gpg-sign feature3) 341 | version = git.get_new_version 342 | version.should eq("2.1.1") 343 | tmp.exec %(git tag "2.1.1") 344 | ensure 345 | tmp.cleanup 346 | end 347 | end 348 | 349 | it "version releases with rebase from master" do 350 | tmp = InTmp.new 351 | 352 | begin 353 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 354 | 355 | tmp.exec %(git init) 356 | tmp.exec %(git checkout -b master) 357 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 358 | tmp.exec %(git tag "1.0.0") 359 | tmp.exec %(git checkout -b dev) 360 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "2") 361 | hash = git.current_commit_hash 362 | version = git.get_new_version 363 | version.should eq("1.0.1-SNAPSHOT.1.#{hash}") 364 | 365 | tmp.exec %(git checkout -b myfeature) 366 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "3") 367 | tmp.exec %(git checkout dev) 368 | tmp.exec %(git merge myfeature) 369 | 370 | tmp.exec %(git checkout master) 371 | tmp.exec %(git rebase --no-gpg-sign dev) 372 | version = git.get_new_version 373 | version.should eq("1.0.1") 374 | ensure 375 | tmp.cleanup 376 | end 377 | end 378 | 379 | it "bump version only once in presence of merge commit message" do 380 | tmp = InTmp.new 381 | 382 | begin 383 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 384 | 385 | tmp.exec %(git init) 386 | tmp.exec %(git checkout -b master) 387 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 388 | tmp.exec %(git tag "1.0.0") 389 | tmp.exec %(git checkout -b dev) 390 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 2") 391 | 392 | tmp.exec %(git checkout master) 393 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "3") 394 | 395 | tmp.exec %(git checkout dev) 396 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "4") 397 | tmp.exec %(git rebase --no-gpg-sign master) 398 | 399 | tmp.exec %(git checkout master) 400 | tmp.exec %(git merge --no-gpg-sign --no-ff dev) 401 | # e.g. commit added when merging by bitbucket, no easy way to produce it automatically... 402 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "Merged xyz (123) breaking:") 403 | 404 | version = git.get_new_version 405 | version.should eq("2.0.0") 406 | ensure 407 | tmp.cleanup 408 | end 409 | end 410 | 411 | it "when in master should not consider pre-release versions for major bumps" do 412 | tmp = InTmp.new 413 | 414 | begin 415 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 416 | 417 | tmp.exec %(git init) 418 | tmp.exec %(git checkout -b master) 419 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 420 | tmp.exec %(git tag "1.0.0") 421 | tmp.exec %(git checkout -b dev) 422 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 2") 423 | 424 | version = git.get_new_version 425 | hash = git.current_commit_hash 426 | tmp.exec %(git tag "#{version}") 427 | version.should eq("2.0.0-SNAPSHOT.1.#{hash}") 428 | 429 | tmp.exec %(git checkout master) 430 | tmp.exec %(git merge --no-gpg-sign --no-ff dev) 431 | 432 | version = git.get_new_version 433 | version.should eq("2.0.0") 434 | ensure 435 | tmp.cleanup 436 | end 437 | end 438 | 439 | it "when in master should not consider pre-release versions for minor bumps" do 440 | tmp = InTmp.new 441 | 442 | begin 443 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 444 | 445 | tmp.exec %(git init) 446 | tmp.exec %(git checkout -b master) 447 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 448 | tmp.exec %(git tag "1.0.0") 449 | tmp.exec %(git checkout -b dev) 450 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "2") 451 | 452 | version = git.get_new_version 453 | hash = git.current_commit_hash 454 | tmp.exec %(git tag "#{version}") 455 | version.should eq("1.0.1-SNAPSHOT.1.#{hash}") 456 | 457 | tmp.exec %(git checkout master) 458 | tmp.exec %(git merge --no-gpg-sign --no-ff dev) 459 | 460 | version = git.get_new_version 461 | version.should eq("1.0.1") 462 | ensure 463 | tmp.cleanup 464 | end 465 | end 466 | 467 | it "bump properly major and reset minor" do 468 | tmp = InTmp.new 469 | 470 | begin 471 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 472 | 473 | tmp.exec %(git init) 474 | tmp.exec %(git checkout -b master) 475 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 476 | tmp.exec %(git tag "0.1.0") 477 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 2") 478 | 479 | version = git.get_new_version 480 | version.should eq("1.0.0") 481 | ensure 482 | tmp.cleanup 483 | end 484 | end 485 | 486 | it "should bump the breaking even with a pre-release tag" do 487 | tmp = InTmp.new 488 | 489 | begin 490 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 491 | 492 | tmp.exec %(git init) 493 | tmp.exec %(git checkout -b master) 494 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 495 | tmp.exec %(git tag "0.1.0") 496 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "feature: 2") 497 | tmp.exec %(git tag "0.2.0-asd") 498 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 2") 499 | 500 | version = git.get_new_version 501 | version.should eq("1.0.0") 502 | ensure 503 | tmp.cleanup 504 | end 505 | end 506 | 507 | it "should bump the breaking even without any other tag" do 508 | tmp = InTmp.new 509 | 510 | begin 511 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 512 | 513 | tmp.exec %(git init) 514 | tmp.exec %(git checkout -b master) 515 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 1") 516 | 517 | version = git.get_new_version 518 | version.should eq("1.0.0") 519 | ensure 520 | tmp.cleanup 521 | end 522 | end 523 | 524 | it "should fallback to tag detection if in detached HEAD(on a tag)" do 525 | tmp = InTmp.new 526 | 527 | begin 528 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 529 | 530 | tmp.exec %(git init) 531 | tmp.exec %(git checkout -b master) 532 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 1") 533 | tmp.exec %(git tag v1) 534 | tmp.exec %(git checkout v1) 535 | 536 | version = git.get_new_version 537 | hash = git.current_commit_hash 538 | version.should eq("1.0.0-v1.1.#{hash}") 539 | ensure 540 | tmp.cleanup 541 | end 542 | end 543 | 544 | it "should properly manage prefixes" do 545 | tmp = InTmp.new 546 | 547 | begin 548 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "v") 549 | 550 | tmp.exec %(git init) 551 | tmp.exec %(git checkout -b master) 552 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "feature: 1") 553 | tmp.exec %(git tag "v1.1.0") 554 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "2") 555 | 556 | version = git.get_new_version 557 | version.should eq("v1.1.1") 558 | ensure 559 | tmp.cleanup 560 | end 561 | end 562 | 563 | it "non-prefixed tags should be ignored if prefix is enabled" do 564 | tmp = InTmp.new 565 | 566 | begin 567 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "v") 568 | 569 | tmp.exec %(git init) 570 | tmp.exec %(git checkout -b master) 571 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 572 | tmp.exec %(git tag "1.0.0") 573 | 574 | version = git.get_new_version 575 | version.should eq("v0.0.1") 576 | ensure 577 | tmp.cleanup 578 | end 579 | end 580 | 581 | it "should properly manage a tag with only prefix" do 582 | tmp = InTmp.new 583 | 584 | begin 585 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "v") 586 | 587 | tmp.exec %(git init) 588 | tmp.exec %(git checkout -b master) 589 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 590 | tmp.exec %(git tag "v") 591 | 592 | version = git.get_new_version 593 | version.should eq("v0.0.1") 594 | ensure 595 | tmp.cleanup 596 | end 597 | end 598 | 599 | it "should count the commits distance" do 600 | tmp = InTmp.new 601 | 602 | begin 603 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 604 | 605 | tmp.exec %(git init) 606 | tmp.exec %(git checkout -b master) 607 | tmp.exec %(git checkout -b v1) 608 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 1") 609 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 2") 610 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 3") 611 | 612 | version = git.get_new_version 613 | hash = git.current_commit_hash 614 | version.should eq("1.0.0-v1.3.#{hash}") 615 | ensure 616 | tmp.cleanup 617 | end 618 | end 619 | 620 | it "ignore non log-path filtered breaking messages" do 621 | tmp = InTmp.new 622 | 623 | begin 624 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "", "dir2/") 625 | 626 | tmp.exec %(git init) 627 | tmp.exec %(git checkout -b master) 628 | tmp.exec %(git checkout -b v1) 629 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 630 | # Create dir1 and tag 1.0.0 631 | tmp.exec %(mkdir dir1 && touch dir1/dummy_file) 632 | tmp.exec %(git add dir1/) 633 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 2") 634 | tmp.exec %(git tag "1.0.0") 635 | # Create dir2 and commit 636 | tmp.exec %(mkdir dir2 && touch dir2/dummy_file) 637 | tmp.exec %(git add dir2/) 638 | tmp.exec %(git commit --no-gpg-sign -m "3") 639 | 640 | # git-version on dir2 should ignore tag on commit with dir1 641 | version = git.get_new_version 642 | hash = git.current_commit_hash 643 | version.should eq("1.0.1-v1.1.#{hash}") 644 | ensure 645 | tmp.cleanup 646 | end 647 | end 648 | 649 | it "ignore log-path filtered breaking messages with multiple paths" do 650 | tmp = InTmp.new 651 | 652 | begin 653 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "", "dir1/ dir3/") 654 | 655 | tmp.exec %(git init) 656 | tmp.exec %(git checkout -b master) 657 | # Create dir1 and tag 1.0.0 658 | base_dir = "dir1" 659 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 660 | tmp.exec %(git add #{base_dir}/) 661 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 1") 662 | tmp.exec %(git tag "1.0.0") 663 | 664 | tmp.exec %(git checkout -b dev) 665 | # Create dir2 and commit breaking (to be ignored) 666 | base_dir = "dir2" 667 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 668 | tmp.exec %(git add #{base_dir}/) 669 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 2") 670 | 671 | # Create dir3 and commit non-breaking 672 | base_dir = "dir3" 673 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 674 | tmp.exec %(git add #{base_dir}/) 675 | tmp.exec %(git commit --no-gpg-sign -m "3") 676 | 677 | tmp.exec %(git checkout master) 678 | tmp.exec %(git merge --no-gpg-sign --no-ff dev) 679 | 680 | # git-version should ignore the breaking tag on commit with dir2 681 | version = git.get_new_version 682 | version.should eq("1.0.1") 683 | ensure 684 | tmp.cleanup 685 | end 686 | end 687 | 688 | it "accept log-path filtered breaking messages with multiple paths" do 689 | tmp = InTmp.new 690 | 691 | begin 692 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "", "dir2/ dir3/") 693 | 694 | tmp.exec %(git init) 695 | tmp.exec %(git checkout -b master) 696 | # Create dir1 and tag 1.0.0 697 | base_dir = "dir1" 698 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 699 | tmp.exec %(git add #{base_dir}/) 700 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 1") 701 | tmp.exec %(git tag "1.0.0") 702 | 703 | tmp.exec %(git checkout -b dev) 704 | # Create dir2 and commit breaking 705 | base_dir = "dir2" 706 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 707 | tmp.exec %(git add #{base_dir}/) 708 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 2") 709 | # Create dir3 and commit non-breaking 710 | base_dir = "dir3" 711 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 712 | tmp.exec %(git add #{base_dir}/) 713 | tmp.exec %(git commit --no-gpg-sign -m "3") 714 | 715 | tmp.exec %(git checkout master) 716 | tmp.exec %(git merge --no-gpg-sign --no-ff dev) 717 | 718 | # git-version should accept the breaking tag on commit with dir2 719 | version = git.get_new_version 720 | version.should eq("2.0.0") 721 | ensure 722 | tmp.cleanup 723 | end 724 | end 725 | 726 | it "accept breaking messages if part of the log-path filter" do 727 | tmp = InTmp.new 728 | 729 | begin 730 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "", "dir1/") 731 | 732 | tmp.exec %(git init) 733 | tmp.exec %(git checkout -b master) 734 | tmp.exec %(git checkout -b v1) 735 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 736 | tmp.exec %(git tag "1.0.0") 737 | 738 | tmp.exec %(mkdir dir1 && touch dir1/dummy_file) 739 | tmp.exec %(git add dir1/) 740 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 2") 741 | 742 | version = git.get_new_version 743 | hash = git.current_commit_hash 744 | version.should eq("2.0.0-v1.1.#{hash}") 745 | ensure 746 | tmp.cleanup 747 | end 748 | end 749 | 750 | it "monorepo log-path filter (multiple dirs, multiple prefixes)" do 751 | tmp = InTmp.new 752 | 753 | begin 754 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "dir2-", "dir2/ dir3/") 755 | 756 | tmp.exec %(git init) 757 | tmp.exec %(git checkout -b master) 758 | 759 | # Create dir1 and tag dir1-1.0.0 760 | base_dir = "dir1" 761 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 762 | tmp.exec %(git add #{base_dir}/) 763 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 1") 764 | tmp.exec %(git tag "dir1-1.0.0") 765 | 766 | # Create dir2 and tag dir2-1.0.0 767 | base_dir = "dir2" 768 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 769 | tmp.exec %(git add #{base_dir}/) 770 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 2") 771 | tmp.exec %(git tag "dir2-1.0.0") 772 | 773 | tmp.exec %(git checkout -b dev) 774 | 775 | # Create dir2 and commit breaking 776 | base_dir = "dir2" 777 | tmp.exec %(mkdir -p #{base_dir} && touch #{base_dir}/dummy_file_2) 778 | tmp.exec %(git add #{base_dir}/) 779 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 3") 780 | 781 | # git-version should accept the breaking tag on commit with dir2 782 | version = git.get_new_version 783 | hash = git.current_commit_hash 784 | version.should eq("dir2-2.0.0-SNAPSHOT.1.#{hash}") 785 | ensure 786 | tmp.cleanup 787 | end 788 | end 789 | it "should truncate long branch names in tags" do 790 | tmp = InTmp.new 791 | 792 | begin 793 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 794 | 795 | tmp.exec %(git init) 796 | tmp.exec %(git checkout -b very-very-very-very-long-branch-name-that-excedes-k8s-limits) 797 | tmp.exec %(git commit -m "commit" --allow-empty) 798 | tmp.exec %(git tag "100.100.100") 799 | 800 | version = git.get_new_version 801 | hash = git.current_commit_hash 802 | version.should eq("100.100.101-veryveryveryverylongbranchname.0.#{hash}") 803 | ensure 804 | tmp.cleanup 805 | end 806 | end 807 | end 808 | 809 | it "get previous version - first commit" do 810 | tmp = InTmp.new 811 | 812 | begin 813 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 814 | 815 | tmp.exec %(git init) 816 | tmp.exec %(git checkout -b master) 817 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 818 | 819 | 820 | # git-version should accept the breaking tag on commit with dir2 821 | version = git.get_previous_version 822 | version.should eq("0.0.0") 823 | ensure 824 | tmp.cleanup 825 | end 826 | end 827 | 828 | it "get previous version - first commit w/ prefix" do 829 | tmp = InTmp.new 830 | 831 | begin 832 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "v") 833 | 834 | tmp.exec %(git init) 835 | tmp.exec %(git checkout -b master) 836 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 837 | 838 | # git-version should accept the breaking tag on commit with dir2 839 | version = git.get_previous_version 840 | version.should eq("v0.0.0") 841 | ensure 842 | tmp.cleanup 843 | end 844 | end 845 | 846 | it "get previous version - pre-tagged" do 847 | tmp = InTmp.new 848 | 849 | begin 850 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "v") 851 | 852 | tmp.exec %(git init) 853 | tmp.exec %(git checkout -b master) 854 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 855 | tmp.exec %(git tag "v1.0.0") 856 | 857 | # git-version should accept the breaking tag on commit with dir2 858 | version = git.get_previous_version 859 | version.should eq("v1.0.0") 860 | ensure 861 | tmp.cleanup 862 | end 863 | end 864 | --------------------------------------------------------------------------------