├── .circleci └── config.yml ├── .github ├── CODEOWNERS └── workflows │ ├── comment_issue.yml │ ├── create_issue.yml │ └── create_issue_on_label.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── action.yml ├── spec ├── git-version-spec.cr └── utils.cr └── src ├── git-version.cr └── main.cr /.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/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @lolgab @ljmf00 @andreaTP @rtfpessoa @bmbferreira @DReigada @pedrocodacy 2 | 3 | *.yml @h314to @paulopontesm 4 | 5 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CRYSTAL?=$(shell which crystal) 2 | CRYSTAL_FLAGS=--release 3 | CRYSTAL_STATIC_FLAGS=--static 4 | 5 | VERSION?=$(shell cat .version) 6 | 7 | all: fmt test build docker_build ## clean and produce target binary and docker image 8 | 9 | .PHONY: test 10 | test: ## runs crystal tests 11 | $(CRYSTAL) spec spec/*.cr 12 | 13 | .PHONY: fmt 14 | fmt: ## format the crystal sources 15 | $(CRYSTAL) tool format 16 | 17 | build: ## compiles from crystal sources 18 | mkdir -p bin 19 | $(CRYSTAL) build $(CRYSTAL_FLAGS) src/main.cr -o bin/git-version 20 | 21 | .PHONY: buildStatic 22 | buildStatic: ## compiles from crystal sources into static binary 23 | mkdir -p bin 24 | crystal build $(CRYSTAL_FLAGS) $(CRYSTAL_STATIC_FLAGS) src/main.cr -o bin/git-version 25 | 26 | docker_build: ## build the docker image 27 | docker build -t codacy/git-version:$(VERSION) . 28 | 29 | .PHONY: clean 30 | clean: ## clean target directories 31 | rm -rf bin 32 | 33 | .PHONY: push-docker-image 34 | push-docker-image: ## push the docker image to the registry (DOCKER_USER and DOCKER_PASS mandatory) 35 | @docker login -u $(DOCKER_USER) -p $(DOCKER_PASS) &&\ 36 | docker build -t codacy/git-version:$(VERSION) . &&\ 37 | docker push codacy/git-version:$(VERSION) 38 | 39 | .PHONY: push-latest-docker-image 40 | push-latest-docker-image: ## push the docker image with the "latest" tag to the registry (DOCKER_USER and DOCKER_PASS mandatory) 41 | @docker login -u $(DOCKER_USER) -p $(DOCKER_PASS) &&\ 42 | docker build -t codacy/git-version:latest . &&\ 43 | docker push codacy/git-version:latest 44 | 45 | .PHONY: help 46 | help: 47 | @echo "make help" 48 | @echo "\n" 49 | @grep -E '^[a-zA-Z_/%\-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 50 | @echo "\n" 51 | -------------------------------------------------------------------------------- /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 | | prefix | The prefix used for the version name | | 193 | | log-paths | The paths used to calculate changes (comma-separated) | | 194 | 195 | ## Requirements 196 | 197 | To use this tool you will need to install a few dependencies: 198 | 199 | Ubuntu: 200 | ``` 201 | sudo apt-get install \ 202 | libevent-dev \ 203 | git 204 | ``` 205 | 206 | Fedora: 207 | ``` 208 | sudo dnf -y install \ 209 | libevent-devel \ 210 | git 211 | ``` 212 | 213 | Alpine: 214 | ``` 215 | apk add --update --no-cache --force-overwrite \ 216 | gc-dev pcre-dev libevent-dev \ 217 | git 218 | ``` 219 | 220 | OsX: 221 | ``` 222 | brew install \ 223 | libevent \ 224 | git 225 | ``` 226 | 227 | 228 | ## CircleCI 229 | 230 | Use this image directly on CircleCI for simple steps 231 | 232 | ``` 233 | version: 2 234 | jobs: 235 | build: 236 | machine: true 237 | working_directory: /app 238 | steps: 239 | - checkout 240 | - run: 241 | name: get new version 242 | command: | 243 | NEW_VERSION=$(docker run --rm -v $(pwd):/repo codacy/git-version) 244 | echo $NEW_VERSION 245 | ``` 246 | 247 | ## Build and Publish 248 | 249 | The pipeline in `circleci` can deploy this for you when the code is pushed to the remote. 250 | 251 | 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) 252 | 253 | You can also run everything locally using the makefile. 254 | 255 | To get the list of available commands: 256 | ``` 257 | $ make help 258 | ``` 259 | 260 | ## Credits 261 | 262 | Great inspiration for this tool has been taken from: [GitVersion](https://github.com/GitTools/GitVersion) 263 | 264 | ## What is Codacy 265 | 266 | [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. 267 | 268 | ### Among Codacy’s features 269 | 270 | - Identify new Static Analysis issues 271 | - Commit and Pull Request Analysis with GitHub, BitBucket/Stash, GitLab (and also direct git repositories) 272 | - Auto-comments on Commits and Pull Requests 273 | - Integrations with Slack, HipChat, Jira, YouTrack 274 | - Track issues in Code Style, Security, Error Proneness, Performance, Unused Code and other categories 275 | 276 | Codacy also helps keep track of Code Coverage, Code Duplication, and Code Complexity. 277 | 278 | Codacy supports PHP, Python, Ruby, Java, JavaScript, and Scala, among others. 279 | 280 | ## Free for Open Source 281 | 282 | Codacy is free for Open Source projects. 283 | 284 | ## License 285 | 286 | git-version is available under the Apache 2 license. See the [LICENSE](./LICENSE) file for more info. 287 | -------------------------------------------------------------------------------- /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 | prefix: 29 | description: 'The prefix to use in the version' 30 | required: false 31 | log-paths: 32 | description: 'The paths to be used to calculate changes (comma-separated)' 33 | required: false 34 | default: ./ 35 | outputs: 36 | version: 37 | description: 'The value of the new pre-calculated tag' 38 | value: ${{ steps.version.outputs.version }} 39 | previous-version: 40 | description: 'Contains the value of previous tag, before calculating a new one' 41 | value: ${{ steps.previous-version.outputs.previous-version }} 42 | runs: 43 | using: "composite" 44 | steps: 45 | - shell: bash 46 | run: | 47 | set -eo pipefail 48 | if [ "${{ inputs.tool-version }}" = "latest" ]; then 49 | download_url="$(curl -Ls https://api.github.com/repos/codacy/git-version/releases/latest | jq -r .assets[0].browser_download_url)" 50 | else 51 | download_url="https://github.com/codacy/git-version/releases/download/${{ inputs.tool-version }}/git-version" 52 | fi 53 | curl -Ls "$download_url" > /usr/local/bin/git-version 54 | chmod +x /usr/local/bin/git-version 55 | - id: previous-version 56 | shell: bash 57 | run: | 58 | set -eo pipefail 59 | 60 | export PREVIOUS_VERSION=$(git-version \ 61 | --previous-version \ 62 | --release-branch "${{ inputs.release-branch }}" \ 63 | --dev-branch "${{ inputs.dev-branch }}" \ 64 | --minor-identifier="${{ inputs.minor-identifier }}" \ 65 | --major-identifier="${{ inputs.major-identifier }}" \ 66 | --version-prefix "${{ inputs.prefix }}") 67 | 68 | echo "previous-version=$PREVIOUS_VERSION" >> $GITHUB_OUTPUT 69 | echo "Previous Version: $PREVIOUS_VERSION" 70 | - id: version 71 | shell: bash 72 | run: | 73 | set -eo pipefail 74 | 75 | export VERSION=$(git-version \ 76 | --release-branch "${{ inputs.release-branch }}" \ 77 | --dev-branch "${{ inputs.dev-branch }}" \ 78 | --minor-identifier="${{ inputs.minor-identifier }}" \ 79 | --major-identifier="${{ inputs.major-identifier }}" \ 80 | --version-prefix "${{ inputs.prefix }}") 81 | 82 | echo "version=$VERSION" >> $GITHUB_OUTPUT 83 | echo "New Version: $VERSION" 84 | -------------------------------------------------------------------------------- /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 properly bump the version" 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 dev) 111 | 112 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 113 | 114 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: XYZ") 115 | 116 | hash = git.current_commit_hash 117 | 118 | version = git.get_new_version 119 | 120 | version.should eq("2.0.0-SNAPSHOT.1.#{hash}") 121 | 122 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: XYZ") 123 | 124 | hash = git.current_commit_hash 125 | 126 | version = git.get_new_version 127 | 128 | version.should eq("2.0.0-SNAPSHOT.2.#{hash}") 129 | 130 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "feature: XYZ") 131 | 132 | hash = git.current_commit_hash 133 | 134 | version = git.get_new_version 135 | 136 | version.should eq("2.0.0-SNAPSHOT.3.#{hash}") 137 | ensure 138 | tmp.cleanup 139 | end 140 | end 141 | 142 | it "bump on master after merging in various ways" do 143 | tmp = InTmp.new 144 | 145 | begin 146 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 147 | 148 | tmp.exec %(git init) 149 | tmp.exec %(git checkout -b master) 150 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 151 | tmp.exec %(git tag "1.0.0") 152 | 153 | tmp.exec %(git checkout -b my-fancy.branch) 154 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "2") 155 | 156 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: XYZ") 157 | 158 | tmp.exec %(git checkout master) 159 | 160 | version = git.get_new_version 161 | 162 | version.should eq("1.0.1") 163 | 164 | tmp.exec %(git merge my-fancy.branch) 165 | 166 | version = git.get_new_version 167 | 168 | version.should eq("2.0.0") 169 | 170 | tmp.exec %(git tag "2.0.0") 171 | 172 | tmp.exec %(git checkout -b my-fancy.branch2) 173 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: ABC") 174 | tmp.exec %(git checkout master) 175 | 176 | version = git.get_new_version 177 | 178 | version.should eq("2.0.1") 179 | 180 | tmp.exec %(git merge --ff-only my-fancy.branch2) 181 | 182 | version = git.get_new_version 183 | 184 | version.should eq("3.0.0") 185 | 186 | tmp.exec %(git tag "3.0.0") 187 | 188 | tmp.exec %(git checkout -b my-fancy.branch3) 189 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "feature: 123") 190 | tmp.exec %(git checkout master) 191 | 192 | version = git.get_new_version 193 | 194 | version.should eq("3.0.1") 195 | 196 | tmp.exec %(git merge --no-gpg-sign --no-ff my-fancy.branch3) 197 | 198 | version = git.get_new_version 199 | 200 | version.should eq("3.1.0") 201 | ensure 202 | tmp.cleanup 203 | end 204 | end 205 | 206 | it "correct version on feature after second commit" do 207 | tmp = InTmp.new 208 | 209 | begin 210 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 211 | 212 | tmp.exec %(git init) 213 | tmp.exec %(git checkout -b master) 214 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 215 | tmp.exec %(git tag "1.0.0") 216 | 217 | tmp.exec %(# Checkout to dev) 218 | tmp.exec %(git checkout -b dev) 219 | 220 | # Checkout to FT-1111 from dev and add a commit) 221 | tmp.exec %(git checkout -b FT-1111) 222 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "3") 223 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "4") 224 | 225 | hash = git.current_commit_hash 226 | 227 | version = git.get_new_version 228 | 229 | version.should eq("1.0.1-ft1111.2.#{hash}") 230 | ensure 231 | tmp.cleanup 232 | end 233 | end 234 | 235 | it "should retrieve correct first version on master" do 236 | tmp = InTmp.new 237 | 238 | begin 239 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 240 | 241 | tmp.exec %(git init) 242 | tmp.exec %(git checkout -b master) 243 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 244 | 245 | version = git.get_new_version 246 | 247 | version.should eq("0.0.1") 248 | ensure 249 | tmp.cleanup 250 | end 251 | end 252 | 253 | it "version properly after 5th commit" do 254 | tmp = InTmp.new 255 | 256 | begin 257 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 258 | 259 | tmp.exec %(git init) 260 | tmp.exec %(git checkout -b master) 261 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 262 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "2") 263 | tmp.exec %(git tag "1.1.0") 264 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "3") 265 | tmp.exec %(git tag "1.2.0") 266 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "4") 267 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "5") 268 | 269 | version = git.get_new_version 270 | 271 | version.should eq("1.2.1") 272 | ensure 273 | tmp.cleanup 274 | end 275 | end 276 | 277 | it "version properly with concurrent features" do 278 | tmp = InTmp.new 279 | 280 | begin 281 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 282 | 283 | tmp.exec %(git init) 284 | tmp.exec %(git checkout -b master) 285 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 286 | tmp.exec %(git tag "1.0.0") 287 | tmp.exec %(git checkout -b feature1) 288 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "feature: 2") 289 | hash = git.current_commit_hash 290 | version = git.get_new_version 291 | version.should eq("1.1.0-feature1.1.#{hash}") 292 | 293 | tmp.exec %(git checkout master) 294 | tmp.exec %(git checkout -b feature2) 295 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 3") 296 | hash = git.current_commit_hash 297 | version = git.get_new_version 298 | version.should eq("2.0.0-feature2.1.#{hash}") 299 | 300 | tmp.exec %(git checkout master) 301 | tmp.exec %(git merge feature2) 302 | version = git.get_new_version 303 | version.should eq("2.0.0") 304 | tmp.exec %(git tag "2.0.0") 305 | 306 | tmp.exec %(git checkout -b feature3) 307 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "4") 308 | hash = git.current_commit_hash 309 | version = git.get_new_version 310 | version.should eq("2.0.1-feature3.1.#{hash}") 311 | 312 | tmp.exec %(git checkout master) 313 | tmp.exec %(git merge --no-gpg-sign feature1) 314 | version = git.get_new_version 315 | version.should eq("2.1.0") 316 | tmp.exec %(git tag "2.1.0") 317 | 318 | tmp.exec %(git merge --no-gpg-sign feature3) 319 | version = git.get_new_version 320 | version.should eq("2.1.1") 321 | tmp.exec %(git tag "2.1.1") 322 | ensure 323 | tmp.cleanup 324 | end 325 | end 326 | 327 | it "version releases with rebase from master" do 328 | tmp = InTmp.new 329 | 330 | begin 331 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 332 | 333 | tmp.exec %(git init) 334 | tmp.exec %(git checkout -b master) 335 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 336 | tmp.exec %(git tag "1.0.0") 337 | tmp.exec %(git checkout -b dev) 338 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "2") 339 | hash = git.current_commit_hash 340 | version = git.get_new_version 341 | version.should eq("1.0.1-SNAPSHOT.1.#{hash}") 342 | 343 | tmp.exec %(git checkout -b myfeature) 344 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "3") 345 | tmp.exec %(git checkout dev) 346 | tmp.exec %(git merge myfeature) 347 | 348 | tmp.exec %(git checkout master) 349 | tmp.exec %(git rebase --no-gpg-sign dev) 350 | version = git.get_new_version 351 | version.should eq("1.0.1") 352 | ensure 353 | tmp.cleanup 354 | end 355 | end 356 | 357 | it "bump version only once in presence of merge commit message" do 358 | tmp = InTmp.new 359 | 360 | begin 361 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 362 | 363 | tmp.exec %(git init) 364 | tmp.exec %(git checkout -b master) 365 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 366 | tmp.exec %(git tag "1.0.0") 367 | tmp.exec %(git checkout -b dev) 368 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 2") 369 | 370 | tmp.exec %(git checkout master) 371 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "3") 372 | 373 | tmp.exec %(git checkout dev) 374 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "4") 375 | tmp.exec %(git rebase --no-gpg-sign master) 376 | 377 | tmp.exec %(git checkout master) 378 | tmp.exec %(git merge --no-gpg-sign --no-ff dev) 379 | # e.g. commit added when merging by bitbucket, no easy way to produce it automatically... 380 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "Merged xyz (123) breaking:") 381 | 382 | version = git.get_new_version 383 | version.should eq("2.0.0") 384 | ensure 385 | tmp.cleanup 386 | end 387 | end 388 | 389 | it "when in master should not consider pre-release versions for major bumps" do 390 | tmp = InTmp.new 391 | 392 | begin 393 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 394 | 395 | tmp.exec %(git init) 396 | tmp.exec %(git checkout -b master) 397 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 398 | tmp.exec %(git tag "1.0.0") 399 | tmp.exec %(git checkout -b dev) 400 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 2") 401 | 402 | version = git.get_new_version 403 | hash = git.current_commit_hash 404 | tmp.exec %(git tag "#{version}") 405 | version.should eq("2.0.0-SNAPSHOT.1.#{hash}") 406 | 407 | tmp.exec %(git checkout master) 408 | tmp.exec %(git merge --no-gpg-sign --no-ff dev) 409 | 410 | version = git.get_new_version 411 | version.should eq("2.0.0") 412 | ensure 413 | tmp.cleanup 414 | end 415 | end 416 | 417 | it "when in master should not consider pre-release versions for minor bumps" do 418 | tmp = InTmp.new 419 | 420 | begin 421 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 422 | 423 | tmp.exec %(git init) 424 | tmp.exec %(git checkout -b master) 425 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 426 | tmp.exec %(git tag "1.0.0") 427 | tmp.exec %(git checkout -b dev) 428 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "2") 429 | 430 | version = git.get_new_version 431 | hash = git.current_commit_hash 432 | tmp.exec %(git tag "#{version}") 433 | version.should eq("1.0.1-SNAPSHOT.1.#{hash}") 434 | 435 | tmp.exec %(git checkout master) 436 | tmp.exec %(git merge --no-gpg-sign --no-ff dev) 437 | 438 | version = git.get_new_version 439 | version.should eq("1.0.1") 440 | ensure 441 | tmp.cleanup 442 | end 443 | end 444 | 445 | it "bump properly major and reset minor" do 446 | tmp = InTmp.new 447 | 448 | begin 449 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 450 | 451 | tmp.exec %(git init) 452 | tmp.exec %(git checkout -b master) 453 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 454 | tmp.exec %(git tag "0.1.0") 455 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m ":breaking: 2") 456 | 457 | version = git.get_new_version 458 | version.should eq("1.0.0") 459 | ensure 460 | tmp.cleanup 461 | end 462 | end 463 | 464 | it "should bump the breaking even with a pre-release tag" do 465 | tmp = InTmp.new 466 | 467 | begin 468 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 469 | 470 | tmp.exec %(git init) 471 | tmp.exec %(git checkout -b master) 472 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 473 | tmp.exec %(git tag "0.1.0") 474 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "feature: 2") 475 | tmp.exec %(git tag "0.2.0-asd") 476 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m ":breaking: 2") 477 | 478 | version = git.get_new_version 479 | version.should eq("1.0.0") 480 | ensure 481 | tmp.cleanup 482 | end 483 | end 484 | 485 | it "should bump the breaking even without any other tag" do 486 | tmp = InTmp.new 487 | 488 | begin 489 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 490 | 491 | tmp.exec %(git init) 492 | tmp.exec %(git checkout -b master) 493 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 1") 494 | 495 | version = git.get_new_version 496 | version.should eq("1.0.0") 497 | ensure 498 | tmp.cleanup 499 | end 500 | end 501 | 502 | it "should fallback to tag detection if in detached HEAD(on a tag)" do 503 | tmp = InTmp.new 504 | 505 | begin 506 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 507 | 508 | tmp.exec %(git init) 509 | tmp.exec %(git checkout -b master) 510 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 1") 511 | tmp.exec %(git tag v1) 512 | tmp.exec %(git checkout v1) 513 | 514 | version = git.get_new_version 515 | hash = git.current_commit_hash 516 | version.should eq("1.0.0-v1.1.#{hash}") 517 | ensure 518 | tmp.cleanup 519 | end 520 | end 521 | 522 | it "should properly manage prefixes" do 523 | tmp = InTmp.new 524 | 525 | begin 526 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "v") 527 | 528 | tmp.exec %(git init) 529 | tmp.exec %(git checkout -b master) 530 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "feature: 1") 531 | tmp.exec %(git tag "v1.1.0") 532 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "2") 533 | 534 | version = git.get_new_version 535 | version.should eq("v1.1.1") 536 | ensure 537 | tmp.cleanup 538 | end 539 | end 540 | 541 | it "non-prefixed tags should be ignored if prefix is enabled" do 542 | tmp = InTmp.new 543 | 544 | begin 545 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "v") 546 | 547 | tmp.exec %(git init) 548 | tmp.exec %(git checkout -b master) 549 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 550 | tmp.exec %(git tag "1.0.0") 551 | 552 | version = git.get_new_version 553 | version.should eq("v0.0.1") 554 | ensure 555 | tmp.cleanup 556 | end 557 | end 558 | 559 | it "should properly manage a tag with only prefix" do 560 | tmp = InTmp.new 561 | 562 | begin 563 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "v") 564 | 565 | tmp.exec %(git init) 566 | tmp.exec %(git checkout -b master) 567 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 568 | tmp.exec %(git tag "v") 569 | 570 | version = git.get_new_version 571 | version.should eq("v0.0.1") 572 | ensure 573 | tmp.cleanup 574 | end 575 | end 576 | 577 | it "should count the commits distance" do 578 | tmp = InTmp.new 579 | 580 | begin 581 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 582 | 583 | tmp.exec %(git init) 584 | tmp.exec %(git checkout -b master) 585 | tmp.exec %(git checkout -b v1) 586 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 1") 587 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 2") 588 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "breaking: 3") 589 | 590 | version = git.get_new_version 591 | hash = git.current_commit_hash 592 | version.should eq("1.0.0-v1.3.#{hash}") 593 | ensure 594 | tmp.cleanup 595 | end 596 | end 597 | 598 | it "ignore non log-path filtered breaking messages" do 599 | tmp = InTmp.new 600 | 601 | begin 602 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "", "dir2/") 603 | 604 | tmp.exec %(git init) 605 | tmp.exec %(git checkout -b master) 606 | tmp.exec %(git checkout -b v1) 607 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 608 | # Create dir1 and tag 1.0.0 609 | tmp.exec %(mkdir dir1 && touch dir1/dummy_file) 610 | tmp.exec %(git add dir1/) 611 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 2") 612 | tmp.exec %(git tag "1.0.0") 613 | # Create dir2 and commit 614 | tmp.exec %(mkdir dir2 && touch dir2/dummy_file) 615 | tmp.exec %(git add dir2/) 616 | tmp.exec %(git commit --no-gpg-sign -m "3") 617 | 618 | # git-version on dir2 should ignore tag on commit with dir1 619 | version = git.get_new_version 620 | hash = git.current_commit_hash 621 | version.should eq("1.0.1-v1.1.#{hash}") 622 | ensure 623 | tmp.cleanup 624 | end 625 | end 626 | 627 | it "ignore log-path filtered breaking messages with multiple paths" do 628 | tmp = InTmp.new 629 | 630 | begin 631 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "", "dir1/ dir3/") 632 | 633 | tmp.exec %(git init) 634 | tmp.exec %(git checkout -b master) 635 | # Create dir1 and tag 1.0.0 636 | base_dir = "dir1" 637 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 638 | tmp.exec %(git add #{base_dir}/) 639 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 1") 640 | tmp.exec %(git tag "1.0.0") 641 | 642 | tmp.exec %(git checkout -b dev) 643 | # Create dir2 and commit breaking (to be ignored) 644 | base_dir = "dir2" 645 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 646 | tmp.exec %(git add #{base_dir}/) 647 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 2") 648 | 649 | # Create dir3 and commit non-breaking 650 | base_dir = "dir3" 651 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 652 | tmp.exec %(git add #{base_dir}/) 653 | tmp.exec %(git commit --no-gpg-sign -m "3") 654 | 655 | tmp.exec %(git checkout master) 656 | tmp.exec %(git merge --no-gpg-sign --no-ff dev) 657 | 658 | # git-version should ignore the breaking tag on commit with dir2 659 | version = git.get_new_version 660 | version.should eq("1.0.1") 661 | ensure 662 | tmp.cleanup 663 | end 664 | end 665 | 666 | it "accept log-path filtered breaking messages with multiple paths" do 667 | tmp = InTmp.new 668 | 669 | begin 670 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "", "dir2/ dir3/") 671 | 672 | tmp.exec %(git init) 673 | tmp.exec %(git checkout -b master) 674 | # Create dir1 and tag 1.0.0 675 | base_dir = "dir1" 676 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 677 | tmp.exec %(git add #{base_dir}/) 678 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 1") 679 | tmp.exec %(git tag "1.0.0") 680 | 681 | tmp.exec %(git checkout -b dev) 682 | # Create dir2 and commit breaking 683 | base_dir = "dir2" 684 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 685 | tmp.exec %(git add #{base_dir}/) 686 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 2") 687 | # Create dir3 and commit non-breaking 688 | base_dir = "dir3" 689 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 690 | tmp.exec %(git add #{base_dir}/) 691 | tmp.exec %(git commit --no-gpg-sign -m "3") 692 | 693 | tmp.exec %(git checkout master) 694 | tmp.exec %(git merge --no-gpg-sign --no-ff dev) 695 | 696 | # git-version should accept the breaking tag on commit with dir2 697 | version = git.get_new_version 698 | version.should eq("2.0.0") 699 | ensure 700 | tmp.cleanup 701 | end 702 | end 703 | 704 | it "accept breaking messages if part of the log-path filter" do 705 | tmp = InTmp.new 706 | 707 | begin 708 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "", "dir1/") 709 | 710 | tmp.exec %(git init) 711 | tmp.exec %(git checkout -b master) 712 | tmp.exec %(git checkout -b v1) 713 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 714 | tmp.exec %(git tag "1.0.0") 715 | 716 | tmp.exec %(mkdir dir1 && touch dir1/dummy_file) 717 | tmp.exec %(git add dir1/) 718 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 2") 719 | 720 | version = git.get_new_version 721 | hash = git.current_commit_hash 722 | version.should eq("2.0.0-v1.1.#{hash}") 723 | ensure 724 | tmp.cleanup 725 | end 726 | end 727 | 728 | it "monorepo log-path filter (multiple dirs, multiple prefixes)" do 729 | tmp = InTmp.new 730 | 731 | begin 732 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "dir2-", "dir2/ dir3/") 733 | 734 | tmp.exec %(git init) 735 | tmp.exec %(git checkout -b master) 736 | 737 | # Create dir1 and tag dir1-1.0.0 738 | base_dir = "dir1" 739 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 740 | tmp.exec %(git add #{base_dir}/) 741 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 1") 742 | tmp.exec %(git tag "dir1-1.0.0") 743 | 744 | # Create dir2 and tag dir2-1.0.0 745 | base_dir = "dir2" 746 | tmp.exec %(mkdir #{base_dir} && touch #{base_dir}/dummy_file) 747 | tmp.exec %(git add #{base_dir}/) 748 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 2") 749 | tmp.exec %(git tag "dir2-1.0.0") 750 | 751 | tmp.exec %(git checkout -b dev) 752 | 753 | # Create dir2 and commit breaking 754 | base_dir = "dir2" 755 | tmp.exec %(mkdir -p #{base_dir} && touch #{base_dir}/dummy_file_2) 756 | tmp.exec %(git add #{base_dir}/) 757 | tmp.exec %(git commit --no-gpg-sign -m "breaking: 3") 758 | 759 | # git-version should accept the breaking tag on commit with dir2 760 | version = git.get_new_version 761 | hash = git.current_commit_hash 762 | version.should eq("dir2-2.0.0-SNAPSHOT.1.#{hash}") 763 | ensure 764 | tmp.cleanup 765 | end 766 | end 767 | it "should truncate long branch names in tags" do 768 | tmp = InTmp.new 769 | 770 | begin 771 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 772 | 773 | tmp.exec %(git init) 774 | tmp.exec %(git checkout -b very-very-very-very-long-branch-name-that-excedes-k8s-limits) 775 | tmp.exec %(git commit -m "commit" --allow-empty) 776 | tmp.exec %(git tag "100.100.100") 777 | 778 | version = git.get_new_version 779 | hash = git.current_commit_hash 780 | version.should eq("100.100.101-veryveryveryverylongbranchname.0.#{hash}") 781 | ensure 782 | tmp.cleanup 783 | end 784 | end 785 | end 786 | 787 | it "get previous version - first commit" do 788 | tmp = InTmp.new 789 | 790 | begin 791 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir) 792 | 793 | tmp.exec %(git init) 794 | tmp.exec %(git checkout -b master) 795 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 796 | 797 | 798 | # git-version should accept the breaking tag on commit with dir2 799 | version = git.get_previous_version 800 | version.should eq("0.0.0") 801 | ensure 802 | tmp.cleanup 803 | end 804 | end 805 | 806 | it "get previous version - first commit w/ prefix" do 807 | tmp = InTmp.new 808 | 809 | begin 810 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "v") 811 | 812 | tmp.exec %(git init) 813 | tmp.exec %(git checkout -b master) 814 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 815 | 816 | # git-version should accept the breaking tag on commit with dir2 817 | version = git.get_previous_version 818 | version.should eq("v0.0.0") 819 | ensure 820 | tmp.cleanup 821 | end 822 | end 823 | 824 | it "get previous version - pre-tagged" do 825 | tmp = InTmp.new 826 | 827 | begin 828 | git = GitVersion::Git.new("dev", "master", "feature:", "breaking:", tmp.@tmpdir, "v") 829 | 830 | tmp.exec %(git init) 831 | tmp.exec %(git checkout -b master) 832 | tmp.exec %(git commit --no-gpg-sign --allow-empty -m "1") 833 | tmp.exec %(git tag "v1.0.0") 834 | 835 | # git-version should accept the breaking tag on commit with dir2 836 | version = git.get_previous_version 837 | version.should eq("v1.0.0") 838 | ensure 839 | tmp.cleanup 840 | end 841 | end 842 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 = "") 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 = if @major_id_is_regex 156 | /#{@major_identifier}/.match(commit) 157 | else 158 | commit.includes?(@major_identifier) 159 | end 160 | if match 161 | previous_version = 162 | SemanticVersion.new( 163 | previous_version.major + 1, 164 | 0, 165 | 0, 166 | previous_version.prerelease, 167 | previous_version.build, 168 | ) 169 | major = true 170 | break 171 | end 172 | end 173 | 174 | if !major 175 | get_commits_since(previous_tag).each do |c| 176 | commit = c.downcase 177 | match = if @minor_id_is_regex 178 | /#{@minor_identifier}/.match(commit) 179 | else 180 | commit.includes?(@minor_identifier) 181 | end 182 | if match 183 | previous_version = 184 | SemanticVersion.new( 185 | previous_version.major, 186 | previous_version.minor + 1, 187 | 0, 188 | previous_version.prerelease, 189 | previous_version.build, 190 | ) 191 | break 192 | end 193 | end 194 | end 195 | 196 | cb = current_branch_or_tag 197 | 198 | if cb == @release_branch 199 | # 200 | elsif cb == @dev_branch 201 | prerelease = [DEV_BRANCH_SUFFIX, commits_distance(previous_tag), current_commit_hash()] of String | Int32 202 | previous_version = 203 | SemanticVersion.new( 204 | previous_version.major, 205 | previous_version.minor, 206 | previous_version.patch, 207 | SemanticVersion::Prerelease.new(prerelease), 208 | nil 209 | ) 210 | else 211 | branch_sanitized_name = cb.downcase.gsub(/[^a-zA-Z0-9]/, "")[0,30] 212 | prerelease = [branch_sanitized_name, commits_distance(previous_tag), current_commit_hash()] of String | Int32 213 | previous_version = 214 | SemanticVersion.new( 215 | previous_version.major, 216 | previous_version.minor, 217 | previous_version.patch, 218 | SemanticVersion::Prerelease.new(prerelease), 219 | nil 220 | ) 221 | end 222 | 223 | return add_prefix(previous_version.to_s) 224 | end 225 | end 226 | end 227 | -------------------------------------------------------------------------------- /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 | prefix = "" 13 | log_paths = "" 14 | 15 | folder = FileUtils.pwd 16 | 17 | OptionParser.parse do |parser| 18 | parser.banner = "Usage: git-version [arguments]" 19 | parser.on("-f FOLDER", "--folder=FOLDER", "Execute the command in the defined folder") { |f| folder = f } 20 | parser.on("-b BRANCH", "--dev-branch=BRANCH", "Specifies the development branch") { |branch| dev_branch = branch } 21 | parser.on("-r BRANCH", "--release-branch=BRANCH", "Specifies the release branch") { |branch| release_branch = branch } 22 | parser.on("--minor-identifier=IDENTIFIER", 23 | "Specifies the string or regex to identify a minor release commit with") { |identifier| minor_identifier = identifier } 24 | parser.on("--major-identifier=IDENTIFIER", 25 | "Specifies the string or regex to identify a major release commit with") { |identifier| major_identifier = identifier } 26 | parser.on("-p PREFIX", "--version-prefix=PREFIX", "Specifies a version prefix") { |p| prefix = p } 27 | parser.on("-l PATH", "--log-paths=PATH", "") { |path| log_paths = path } 28 | parser.on("--previous-version", "Returns the previous tag instead of calculating a new one") { previous_version=true } 29 | parser.on("-h", "--help", "Show this help") { puts parser } 30 | parser.invalid_option do |flag| 31 | STDERR.puts "ERROR: #{flag} is not a valid option." 32 | STDERR.puts parser 33 | exit(1) 34 | end 35 | end 36 | 37 | git = GitVersion::Git.new(dev_branch, release_branch, minor_identifier, major_identifier, folder, prefix, log_paths) 38 | 39 | if previous_version 40 | puts "#{git.get_previous_version}" 41 | else 42 | puts "#{git.get_new_version}" 43 | end 44 | --------------------------------------------------------------------------------