├── .github └── workflows │ ├── build.yaml │ └── pr.yaml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── functionalTest ├── kotlin │ └── uk │ │ └── co │ │ └── developmentanddinosaurs │ │ └── gradle │ │ └── gitversioner │ │ ├── tasks │ │ ├── PrintVersionTaskSpec.kt │ │ └── TagVersionTaskSpec.kt │ │ └── util │ │ ├── Gradle.kt │ │ └── Project.kt └── resources │ ├── configured-build.gradle │ ├── configured-build.gradle.kts │ ├── default-build.gradle │ ├── default-build.gradle.kts │ └── settings.gradle ├── integrationTest └── kotlin │ └── uk │ └── co │ └── developmentanddinosaurs │ └── gradle │ └── gitversioner │ └── core │ ├── GitTaggerSpec.kt │ └── VersionerSpec.kt ├── main └── kotlin │ └── uk │ └── co │ └── developmentanddinosaurs │ └── gradle │ └── gitversioner │ ├── VersionerPlugin.kt │ ├── configuration │ ├── ConfigurationException.kt │ ├── Match.kt │ ├── Pattern.kt │ ├── StartFrom.kt │ ├── Tag.kt │ ├── TaggerExtensionConfig.kt │ ├── VersionerExtension.kt │ ├── VersionerExtensionConfig.kt │ └── git │ │ ├── Authentication.kt │ │ ├── Git.kt │ │ ├── Https.kt │ │ └── Ssh.kt │ ├── core │ ├── tag │ │ ├── GitTagger.kt │ │ └── TaggerConfig.kt │ └── version │ │ ├── Version.kt │ │ ├── Versioner.kt │ │ └── VersionerConfig.kt │ └── tasks │ ├── PrintVersionTask.kt │ └── TagVersionTask.kt └── test ├── kotlin └── uk │ └── co │ └── developmentanddinosaurs │ └── gradle │ └── gitversioner │ ├── FileExtensions.kt │ ├── configuration │ ├── ConfigurationExceptionSpec.kt │ └── VersionerExtensionTest.kt │ └── version │ └── VersionSpec.kt └── resources ├── configured-build.gradle └── default-build.gradle /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: 🛒 Checkout code 11 | uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | - name: ☕ Set up JDK 15 | uses: actions/setup-java@v3 16 | with: 17 | distribution: corretto 18 | java-version: 17 19 | - name: 🐘 Gradle build 20 | uses: burrunan/gradle-cache-action@v1 21 | with: 22 | arguments: build 23 | concurrent: true 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | - name: ⬆️ Publish Jar 27 | env: 28 | GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} 29 | GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} 30 | run: ./gradlew publishPlugins 31 | - name: 🏷️ Tag version 32 | env: 33 | TOKEN: ${{ secrets.TOKEN }} 34 | run: ./gradlew tagVersion 35 | -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | name: PR Build 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: 🛒 Checkout code 11 | uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | - name: ☕ Set up JDK 15 | uses: actions/setup-java@v3 16 | with: 17 | distribution: corretto 18 | java-version: 17 19 | - name: 🐘 Gradle build 20 | uses: burrunan/gradle-cache-action@v1 21 | with: 22 | arguments: build 23 | concurrent: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ 2 | *.iml 3 | .idea 4 | out/ 5 | 6 | # Gradle 7 | .gradle 8 | build/ 9 | 10 | # Mac 11 | .DS_Store 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Git Versioner Project and Developer Guidelines 2 | 3 | Follow these easy steps and guidelines to ensure swift acceptance of pull requests into Git Versioner and the improved 4 | likelihood of seeing your changes reflected in a release version sooner. 5 | 6 | ## Contribution Workflow 7 | 8 | Following the contribution workflow will ensure that your development won't go unnoticed and will be merged into the 9 | main repository in a quicker timeframe than if the workflow isn't followed. 10 | 11 | 1. Open an issue in GitHub with the bug, improvement, or feature request with information about your proposed changes. 12 | This will allow discussion over the changes to occur. 13 | 14 | 2. Fork the repository. 15 | 16 | 3. Make your changes and submit a pull request from your fork into the main branch. 17 | 18 | 4. The pull request will run through GitHub Actions to ensure it compiles and tests pass. 19 | 20 | 5. The pull request will be reviewed and comments made and changes requests where required. 21 | 22 | 6. When ready, the pull request will be merged into the main branch and automatically included in the next release. 23 | 24 | ## Contribution Guidelines 25 | 26 | Contributions will be merged in a lot quicker if you follow these guidelines: 27 | 28 | 1. Include unit tests for all new logic introduced. 29 | 30 | 2. Integration tests should be included where sensible. 31 | 32 | 3. Include documentation in the README for new features. 33 | 34 | ## Commit Message Guidelines 35 | 36 | We use commit messages as a form of documentation, so it's important that they contain enough information to be useful. 37 | 38 | 1. Use a concise title for the commit summary line. 39 | 40 | 2. Include extra information in the main body of the commit message. This can be in bullet points or sentences, and is 41 | supposed to give context around why the change was made and reasons for the design decisions made, and any 42 | shortcomings of the solution that may need to be looked at again. 43 | 44 | 3. Keep commits discrete and self-contained. Change one thing per commit, and ensure each commit makes sense in 45 | isolation. 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Tyrannoseanus 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gradle Git Versioner 2 | 3 | [![MIT License](https://img.shields.io/github/license/development-and-dinosaurs/gradle-git-versioner?style=for-the-badge&logo=pagekit)](https://github.com/development-and-dinosaurs/gradle-git-versioner/blob/main/LICENSE) 4 | 5 | ## What is Git Versioner? 6 | 7 | Git Versioner is the coolest way to automatically increase your version numbers. 8 | 9 | With Git Versioner, you leave behind the manual process around updating your project version and have it handled 10 | completely automatically by the plugin instead. All you have to decide is when a change should be major, minor, or patch 11 | and Git Versioner will do the rest. 12 | 13 | Git Versioner takes the difficulty out of manually incrementing and changing version numbers in your project, using git 14 | commit messages and semantic versioning principles. 15 | 16 | ## How does it work? 17 | 18 | It's really simple actually - Git Versioner will inspect your git commit history, checking for places that have been 19 | designated as major, minor, or patch commits. It then tallies them up to come up with the current version number. 20 | 21 | It's customisable too - you can decide what to look for to decide whether something is a major, minor, or patch update. 22 | You can even tell it a version number to start from. 23 | 24 | ## How do I migrate to using Git Versioner? 25 | 26 | That's really simple too. 27 | 28 | 1. Find us on the [Gradle Plugin Portal](https://plugins.gradle.org/plugin/uk.co.developmentanddinosaurs.git-versioner). 29 | 30 | 2. Apply the plugin using your preferred syntax. 31 | 32 | 3. You're ready to go - your versioning will now be taken care of by Git Versioner. 33 | 34 | ## How should I use it? 35 | 36 | This one comes down to personal preference, but the way I like to version is as follows: 37 | 38 | 1. Start a branch with the feature I'm working on. 39 | 40 | 2. Make atomic commits for each of the changes I'm doing with useful commit messages. 41 | 42 | 3. Raise a pull request for that feature back into the main branch. 43 | 44 | 4. Merge the pull request in using a merge commit, adding the version tag to the commit message. 45 | 46 | 5. Publish from the main branch. 47 | 48 | This ensures that you keep a nice commit history and end up with the correct version on the main branch. 49 | 50 | ## How do I use it? 51 | 52 | You just need to change the way you're doing commits. This changes a bit depending on whether you're a new project, or a 53 | project with an existing versioning scheme. 54 | 55 | ### I'm a new project 56 | 57 | Great, that makes things really easy. All you have to do is make sure to tag your commits as you're making them. You can 58 | do this on a commit by commit basis, or you can do it on merge commits. Up to you. 59 | 60 | The default tagging is to include `[major]` for a major change, `[minor]` for a minor change, and `[patch]` for a patch 61 | change. All other changes will be treated as `commit` changes. 62 | 63 | ### I'm an existing project doing versioning a different way 64 | 65 | Ok, well you should stop that right now and do it this way instead. In general, you just have to follow the step above, 66 | then do a bit extra. 67 | 68 | You probably don't want to start from version 0.0.1 again, right? You've got two options, you can do some configuration, 69 | or you can rewrite your history. 70 | 71 | #### Let's do some configuration 72 | 73 | Good idea - to use configuration, add the following block to your gradle file: 74 | 75 | ```groovy 76 | versioner { 77 | startFrom { 78 | major = 1 79 | minor = 3 80 | patch = 5 81 | } 82 | } 83 | ``` 84 | 85 | This sets your current version to 1.3.5. Obviously set this to match the version that your project is actually currently 86 | on. 87 | 88 | Now each time you make a major, minor, or patch commit, the calculations will be applied on top of version 1.3.5. So 89 | your first patch commit using Git Versioner will make it version 1.3.6. The first minor commit will make it version 90 | 1.4.0, etc. 91 | 92 | #### You mentioned rewriting history? 93 | 94 | The nuclear option is to rewrite the project history completely and act like you were using Git Versioner all along ( 95 | Like you should have been anyway, right?). 96 | 97 | This is really not the best idea for large or established projects, as you're going to make every single contributor sad 98 | by pulling the history out from under them. If you only have a small or solo project, it's usually not such a big deal. 99 | 100 | If you still want to do it, just go for a `git rebase -i `, mark any of the commits where you've 101 | updated the version to be reworded, then one-by-one update the commit messages to include either `[major]`, `[minor]`, 102 | or `[patch]`. 103 | 104 | If you've done it right, the version produced by Git Versioner will be the same as your current version. Pretty neat 105 | right? 106 | 107 | ## Can I use different phrases to bump the version instead? 108 | 109 | Yes! You just need to override the match defaults using some configuration. Here's an example: 110 | 111 | ``` 112 | versioner { 113 | match { 114 | major = "+semver: major" 115 | minor = "+semver: minor" 116 | patch = "+semver: patch" 117 | } 118 | } 119 | ``` 120 | 121 | ## I don't really like the versioning pattern you're using 122 | 123 | Me neither, but the default is going to stay that way to maintain backwards compatibility. 124 | 125 | Fortunately you're able to define your own pattern for the version string. This is the pattern that will apply to 126 | the `project.version` variable, and the git tag after the prefix. You define it like this: 127 | 128 | ```groovy 129 | versioner { 130 | pattern { 131 | pattern = "%M.%m.%p(.%c)" 132 | } 133 | } 134 | ``` 135 | 136 | That's an example of the default pattern. The pattern matching works off of simple string substitution with a little 137 | conditional logic built in based off the commit number. 138 | 139 | The substitutions you can use are as follows: 140 | 141 | | pattern | description | 142 | |---------|----------------------------------| 143 | | %M | major number | 144 | | %m | minor number | 145 | | %p | patch number | 146 | | %c | commit number | 147 | | %b | current branch | 148 | | %H | full hash of the current commit | 149 | | %h | short hash of the current commit | 150 | | () | show when commit number > 0 | 151 | 152 | Here's a list of some example patterns, and the output you get for particular versions to make it more obvious how it 153 | works: 154 | 155 | | version | pattern | output | 156 | |---------|---------------------|------------------| 157 | | 1.2.3.4 | %M.%m.%p.%c | 1.2.3.4 | 158 | | 1.2.3.0 | %M.%m.%p.%c | 1.2.3.0 | 159 | | 1.2.3.4 | %M.%m.%p(.%c) | 1.2.3.4 | 160 | | 1.2.3.0 | %M.%m.%p(.%c) | 1.2.3 | 161 | | 1.2.3.4 | %M.%m.%p-%c | 1.2.3-4 | 162 | | 1.2.3.4 | %M.%m(-SNAPSHOT) | 1.2-SNAPSHOT | 163 | | 1.2.3.0 | %M.%m.%p(-SNAPSHOT) | 1.2.3 | 164 | | 1.2.3.4 | %M.%m.%p-%H | 1.2.3-hash123456 | 165 | | 1.2.3.4 | %M.%m.%p-%h | 1.2.3-hash123 | 166 | | 1.2.3.4 | %M.%m.%p-%b | 1.2.3-main | 167 | 168 | Pattern calculation uses really simple string substitution and regular expressions, so there's nothing fancy like 169 | pattern escaping or things like that. 170 | 171 | This does mean that you can't use parentheses in your version string, and if you try to have something like 172 | %MaybeThisIsAGoodIdea, it won't go very well. 173 | 174 | ## Can I tag things in git? 175 | 176 | Git tagging functionality is now included within Git Versioner itself, and you can use it by simply running 177 | the `tagVersion` task. This will create a local tag called `v by default, but you can customise the version 178 | prefix like so: 179 | 180 | ```groovy 181 | versioner { 182 | tag { 183 | prefix = "V" 184 | } 185 | } 186 | ``` 187 | 188 | This changes the tag prefix to use an uppercase 'V' instead of the standard lowercase 'v', but you can 189 | be as creative as you like with this. 190 | 191 | You can also choose to include a message with this tag. Right now the only options are to forgo a message or to use the 192 | last commit message as the tag message. You can turn on this functionality like so: 193 | 194 | ```groovy 195 | versioner { 196 | tag { 197 | useCommitMessage = true 198 | } 199 | } 200 | ``` 201 | 202 | This is useful if you take up the habit of writing a meaningful commit message for your release commit. For example, 203 | which is the better release message for a new piece of functionality? 204 | 205 | ``` 206 | Merge pull request #4 from feature/annotated-tags [minor] 207 | ``` 208 | 209 | ``` 210 | Release: Add annotated tag messaging 211 | 212 | This release includes the ability to specify tag messages using the message of the latest commit. 213 | 214 | [minor] 215 | ``` 216 | 217 | ## How do I authenticate to push my tags? 218 | 219 | The plugin supports both SSH or HTTPS to connect to your remote serve. 220 | 221 | If you're using an SSH key to push to your remote server, your server is using an RSA host key, and you're not using a 222 | passphrase, then you won't have to do anything. The push to the remote server will use your SSH key, and it will have no 223 | problem connecting to your remote server. 224 | 225 | If you're using a passphrase then you're out of luck as that isn't currently supported. 226 | 227 | If you're using an SSH key, but your server is using an ECDSA host key, then we have a little problem. This plugin uses 228 | JGit to interact with your git repository, which doesn't correctly handle ECDSA keys. Your options for this scenario are 229 | to either use an RSA host key, turn off strict host key checking, or use HTTPS. 230 | 231 | You can turn off strict host key checking in the configuration like so: 232 | 233 | ```groovy 234 | versioner { 235 | git { 236 | authentication { 237 | ssh { 238 | strictSsl = false 239 | } 240 | } 241 | } 242 | } 243 | ``` 244 | 245 | I don't really know why I called it strictSsl. I'll rename it to be strictHostKeyChecking or something like that 246 | eventually maybe and hope nobody notices. 247 | 248 | If you don't like the idea of turning off host checking, you can use HTTPS instead. To do this you need to make sure 249 | your remote repository is using HTTPS rather than SSH. You can configure either username and password, or you can use an 250 | access token. The configuration for this method looks like so: 251 | 252 | ```groovy 253 | versioner { 254 | git { 255 | authentication { 256 | https { 257 | username = "username" 258 | password = "password" 259 | token = "token" 260 | } 261 | } 262 | } 263 | } 264 | ``` 265 | 266 | If you don't want to hard code your credentials in your gradle build file, then you can pass them in as a property 267 | instead, like this: 268 | 269 | ```groovy 270 | versioner { 271 | git { 272 | authentication { 273 | https { 274 | token = project.findProperty("token") 275 | } 276 | } 277 | } 278 | } 279 | ``` 280 | 281 | ## My version is "unspecified" 282 | 283 | This is a common problem stemming from the configuration and build phases in Gradle, which can trip a lot of people up. 284 | This is ok though, because we can work around this to solve the problem. 285 | 286 | First though, a little history lesson. I built the original architecture for this plugin with the java plugin in mind- 287 | namely the `jar` task so that project artefacts could be versioned more easily. The jar task doesn't resolve the version 288 | until the execution stage, so it was safe to set the version at the end of the configuration stage- just in time for the 289 | jar task. 290 | 291 | Unfortunately this makes it more difficult if you want to access the version in the configuration stage, as it won't 292 | have been set yet. There are a couple of things you can do: 293 | 294 | 1. Don't access the version in the configuration stage, and access it from the execution stage instead. If you're 295 | writing your own task, this means using the version in the `doFirst` or `doLast` closures instead of the 296 | configuration body. 297 | 298 | 2. Access it at the end of the configuration stage by wrapping your configuration in a `project.afterEvaluate` block. 299 | This is how the version gets set in the first place, so should work correctly. 300 | 301 | 3. Manually apply the version before you need to use it. You can call the `apply()` method on the `versioner` extension 302 | to force resolution of the version and make it available from the point that you make the call. 303 | 304 | Here are some examples of when to access the version and what it will be set to: 305 | 306 | ```groovy 307 | task version() { 308 | println project.version // prints unspecified - this is too early in the configuration phase 309 | project.afterEvaluate { 310 | println project.version // prints version - configuration stage but late enough to be calculated 311 | } 312 | doFirst { 313 | println project.version // prints version - execution stage, so late enough to be calculated 314 | } 315 | doLast { 316 | println project.version // prints version - execution stage, so late enough to be calculated 317 | } 318 | } 319 | 320 | versioner.apply() 321 | 322 | task version2() { 323 | println project.version // prints version - version has been applied before this line is evaluated 324 | } 325 | ``` 326 | 327 | ## This is so cool, how do I contribute? 328 | 329 | I know right? You should check out the [contribution guide](CONTRIBUTING.md). 330 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | `kotlin-dsl` 5 | kotlin("jvm") version "1.9.20" 6 | id("com.diffplug.spotless") version "6.23.3" 7 | id("com.gradle.plugin-publish") version "1.2.1" 8 | id("io.toolebox.git-versioner") version "1.6.7" 9 | id("pl.droidsonroids.jacoco.testkit") version "1.0.12" 10 | } 11 | 12 | group = "uk.co.developmentanddinosaurs" 13 | 14 | setUpExtraTests("functional") 15 | setUpExtraTests("integration") 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | compileOnly(gradleApi()) 23 | implementation("org.eclipse.jgit:org.eclipse.jgit:5.9.0.202009080501-r") 24 | implementation("com.jcraft:jsch:0.1.55") 25 | 26 | val kotestVersion = "5.8.0" 27 | testImplementation("io.kotest:kotest-assertions-core:$kotestVersion") 28 | testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion") 29 | testImplementation("io.mockk:mockk:1.13.7") 30 | } 31 | 32 | gradlePlugin { 33 | website = "https://github.com/development-and-dinosaurs/gradle-git-versioner" 34 | vcsUrl = "https://github.com/development-and-dinosaurs/gradle-git-versioner" 35 | plugins { 36 | create("versionerPlugin") { 37 | id = "uk.co.developmentanddinosaurs.git-versioner" 38 | displayName = "Git Versioner Plugin" 39 | description = "Automatically version a project based on commit messages and semantic versioning principles" 40 | implementationClass = "uk.co.developmentanddinosaurs.gradle.gitversioner.VersionerPlugin" 41 | tags = listOf("git", "version", "semantic-version") 42 | } 43 | } 44 | testSourceSets(sourceSets["functionalTest"]) 45 | } 46 | 47 | val setupPluginSecrets = 48 | tasks.create("setupPluginSecrets") { 49 | doLast { 50 | System.setProperty("gradle.publish.key", System.getenv("GRADLE_PUBLISH_KEY")) 51 | System.setProperty("gradle.publish.secret", System.getenv("GRADLE_PUBLISH_SECRET")) 52 | } 53 | } 54 | tasks["publishPlugins"].dependsOn(setupPluginSecrets) 55 | 56 | spotless { 57 | kotlin { 58 | target("**/*.kt") 59 | ktlint() 60 | } 61 | kotlinGradle { 62 | target("*.gradle.kts") 63 | ktlint() 64 | } 65 | } 66 | 67 | versioner { 68 | git { 69 | authentication { 70 | https { 71 | token = System.getenv("TOKEN") 72 | } 73 | } 74 | } 75 | tag { 76 | useCommitMessage = true 77 | } 78 | } 79 | 80 | java { 81 | toolchain { 82 | languageVersion = JavaLanguageVersion.of(8) 83 | } 84 | } 85 | 86 | tasks.withType { 87 | kotlinOptions.jvmTarget = "1.8" 88 | } 89 | 90 | tasks.withType { 91 | useJUnitPlatform() 92 | finalizedBy("jacocoTestReport") 93 | } 94 | 95 | tasks.jacocoTestReport { 96 | executionData(fileTree(projectDir).include("build/jacoco/*.exec")) 97 | reports { 98 | xml.required = true 99 | } 100 | } 101 | 102 | jacocoTestKit { 103 | applyTo("functionalTestRuntimeOnly", tasks.named("functionalTest")) 104 | } 105 | 106 | /** 107 | * Sets up an extra test source set, configuration, and task to run the tests. 108 | * 109 | * Can be used to easily set up a new type of test to run - for example, integration, and functional tests. 110 | */ 111 | fun setUpExtraTests(type: String) { 112 | val test = "${type}Test" 113 | sourceSets.register(test) { 114 | compileClasspath += sourceSets.main.get().output + sourceSets.test.get().output 115 | runtimeClasspath += sourceSets.main.get().output + sourceSets.test.get().output 116 | } 117 | 118 | configurations["${test}Implementation"].extendsFrom(configurations["testImplementation"]) 119 | 120 | tasks.register(test, Test::class.java) { 121 | doNotTrackState("jacoco") 122 | description = "Runs the $type tests" 123 | group = "verification" 124 | testClassesDirs = sourceSets[test].output.classesDirs 125 | classpath = sourceSets[test].runtimeClasspath 126 | dependsOn("test") 127 | tasks["check"].dependsOn(this) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/development-and-dinosaurs/gradle-git-versioner/fff25bce9d29075376b10d8d8653b4bda731e981/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'gradle-git-versioner' 2 | -------------------------------------------------------------------------------- /src/functionalTest/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/tasks/PrintVersionTaskSpec.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.tasks 2 | 3 | import io.kotest.core.spec.style.StringSpec 4 | import io.kotest.matchers.string.shouldContain 5 | import uk.co.developmentanddinosaurs.gradle.gitversioner.util.Gradle 6 | import uk.co.developmentanddinosaurs.gradle.gitversioner.util.Project 7 | import java.io.File 8 | 9 | class PrintVersionTaskSpec : StringSpec({ 10 | 11 | val directory = File("build/tmp/functionalTest/PrintVersionTaskSpec") 12 | val project = Project.createProject(directory) 13 | val gradle = Gradle(directory) 14 | 15 | fun addCommits(project: Project) { 16 | project 17 | .withCommit("trex") 18 | .withCommit("stego") 19 | .withCommit("compy") 20 | .withCommit("[major]") 21 | .withCommit("[minor]") 22 | .withCommit("[patch]") 23 | .withCommit("message") 24 | } 25 | 26 | afterTest { 27 | directory.deleteRecursively() 28 | } 29 | 30 | "prints version using default configuration when none is supplied in Groovy" { 31 | project.withSettingsFile().withGit().withGroovyGradleFile("default") 32 | addCommits(project) 33 | 34 | val result = gradle.runTask("printVersion") 35 | 36 | result.output shouldContain "1.1.1.1" 37 | } 38 | 39 | "prints version using provided configuration when supplied in Groovy" { 40 | project.withSettingsFile().withGit().withGroovyGradleFile("configured") 41 | addCommits(project) 42 | 43 | val result = gradle.runTask("printVersion") 44 | 45 | result.output shouldContain "2.1.1-4" 46 | } 47 | 48 | "prints version using default configuration when none is supplied in Kotlin" { 49 | project.withSettingsFile().withGit().withKotlinGradleFile("default") 50 | addCommits(project) 51 | 52 | val result = gradle.runTask("printVersion") 53 | 54 | result.output shouldContain "1.1.1.1" 55 | } 56 | 57 | "prints version using provided configuration when supplied in Kotlin" { 58 | project.withSettingsFile().withGit().withKotlinGradleFile("configured") 59 | addCommits(project) 60 | 61 | val result = gradle.runTask("printVersion") 62 | 63 | result.output shouldContain "2.1.1-4" 64 | } 65 | }) 66 | -------------------------------------------------------------------------------- /src/functionalTest/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/tasks/TagVersionTaskSpec.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.tasks 2 | 3 | import io.kotest.core.spec.style.StringSpec 4 | import io.kotest.matchers.shouldBe 5 | import org.eclipse.jgit.api.Git 6 | import org.eclipse.jgit.revwalk.RevWalk 7 | import org.eclipse.jgit.transport.URIish 8 | import uk.co.developmentanddinosaurs.gradle.gitversioner.util.Gradle 9 | import uk.co.developmentanddinosaurs.gradle.gitversioner.util.Project 10 | import java.io.File 11 | 12 | class TagVersionTaskSpec : StringSpec({ 13 | 14 | val directory = File("build/tmp/functionalTest/TagVersionTaskSpec/local") 15 | val remoteDir = File("build/tmp/functionalTest/TagVersionTaskSpec/remote") 16 | val project = Project.createProject(directory) 17 | val gradle = Gradle(directory) 18 | lateinit var localGit: Git 19 | lateinit var remoteGit: Git 20 | 21 | fun lastTag(git: Git) = RevWalk(git.repository).parseTag(git.tagList().call()[0].objectId) 22 | 23 | fun createRepository(folder: File): Git { 24 | return Git.init().setDirectory(folder).call() 25 | } 26 | 27 | fun addRemote(git: Git) { 28 | git.remoteAdd().setName("origin").setUri(URIish(remoteDir.absolutePath)).call() 29 | } 30 | 31 | fun addCommit(git: Git) { 32 | git.commit().setSign(false).setAllowEmpty(true).setMessage("[major]").call() 33 | } 34 | 35 | beforeTest { 36 | localGit = createRepository(directory) 37 | remoteGit = createRepository(remoteDir) 38 | addRemote(localGit) 39 | addCommit(localGit) 40 | } 41 | 42 | afterTest { 43 | directory.deleteRecursively() 44 | remoteDir.deleteRecursively() 45 | } 46 | 47 | "Creates tag locally and pushes to remote repository using default configuration when none is supplied in Groovy" { 48 | project.withSettingsFile().withGroovyGradleFile("default") 49 | 50 | gradle.runTask("tagVersion") 51 | 52 | lastTag(localGit).tagName shouldBe "v1.0.0" 53 | lastTag(remoteGit).tagName shouldBe "v1.0.0" 54 | } 55 | 56 | "Creates tag locally and pushes to remote repository using provided configuration when supplied in Groovy" { 57 | project.withSettingsFile().withGroovyGradleFile("configured") 58 | 59 | gradle.runTask("tagVersion") 60 | 61 | lastTag(localGit).tagName shouldBe "x1.1.1-1" 62 | lastTag(remoteGit).tagName shouldBe "x1.1.1-1" 63 | } 64 | 65 | "Creates tag locally and pushes to remote repository using default configuration when none is supplied in Kotlin" { 66 | project.withSettingsFile().withKotlinGradleFile("default") 67 | 68 | gradle.runTask("tagVersion") 69 | 70 | lastTag(localGit).tagName shouldBe "v1.0.0" 71 | lastTag(remoteGit).tagName shouldBe "v1.0.0" 72 | } 73 | 74 | "Creates tag locally and pushes to remote repository using provided configuration when supplied in Kotlin" { 75 | project.withSettingsFile().withKotlinGradleFile("configured") 76 | 77 | gradle.runTask("tagVersion") 78 | 79 | lastTag(localGit).tagName shouldBe "x1.1.1-1" 80 | lastTag(remoteGit).tagName shouldBe "x1.1.1-1" 81 | } 82 | }) 83 | -------------------------------------------------------------------------------- /src/functionalTest/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/util/Gradle.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.util 2 | 3 | import org.gradle.testkit.runner.BuildResult 4 | import org.gradle.testkit.runner.GradleRunner 5 | import java.io.File 6 | 7 | class Gradle(private val directory: File) { 8 | fun runTask(name: String): BuildResult { 9 | return GradleRunner.create() 10 | .withProjectDir(directory) 11 | .withArguments(name, "-q") 12 | .withPluginClasspath() 13 | .withJacoco() 14 | .forwardOutput() 15 | .build() 16 | } 17 | 18 | fun GradleRunner.withJacoco(): GradleRunner { 19 | File("build/testkit/functionalTest/testkit-gradle.properties") 20 | .copyTo(File(projectDir, "gradle.properties")) 21 | return this 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/functionalTest/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/util/Project.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.util 2 | 3 | import org.eclipse.jgit.api.Git 4 | import java.io.File 5 | 6 | class Project(private val directory: File) { 7 | private lateinit var git: Git 8 | 9 | companion object { 10 | fun createProject(directory: File) = Project(directory) 11 | } 12 | 13 | fun withGit(): Project { 14 | git = Git.init().setDirectory(directory).call() 15 | return this 16 | } 17 | 18 | fun withCommit(message: String): Project { 19 | git.commit().setSign(false).setAllowEmpty(true).setMessage(message).call() 20 | return this 21 | } 22 | 23 | fun withGroovyGradleFile(name: String): Project { 24 | return withGradleFile("$name-build.gradle", "build.gradle") 25 | } 26 | 27 | fun withKotlinGradleFile(name: String): Project { 28 | return withGradleFile("$name-build.gradle.kts", "build.gradle.kts") 29 | } 30 | 31 | fun withSettingsFile(): Project { 32 | return withGradleFile("settings.gradle", "settings.gradle") 33 | } 34 | 35 | private fun withGradleFile( 36 | name: String, 37 | destination: String, 38 | ): Project { 39 | File("src/functionalTest/resources/$name").copyTo(File(directory, destination)) 40 | return this 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/functionalTest/resources/configured-build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'uk.co.developmentanddinosaurs.git-versioner' 3 | } 4 | 5 | versioner { 6 | startFrom { 7 | major = 1 8 | minor = 1 9 | patch = 1 10 | } 11 | match { 12 | major = "trex" 13 | minor = "stego" 14 | patch = "compy" 15 | } 16 | tag { 17 | prefix = "x" 18 | } 19 | pattern { 20 | pattern = "%M.%m.%p(-%c)" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/functionalTest/resources/configured-build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("uk.co.developmentanddinosaurs.git-versioner") 3 | } 4 | 5 | versioner { 6 | startFrom { 7 | major = 1 8 | minor = 1 9 | patch = 1 10 | } 11 | match { 12 | major = "trex" 13 | minor = "stego" 14 | patch = "compy" 15 | } 16 | tag { 17 | prefix = "x" 18 | } 19 | pattern { 20 | pattern = "%M.%m.%p(-%c)" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/functionalTest/resources/default-build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'uk.co.developmentanddinosaurs.git-versioner' 3 | } 4 | -------------------------------------------------------------------------------- /src/functionalTest/resources/default-build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("uk.co.developmentanddinosaurs.git-versioner") 3 | } 4 | -------------------------------------------------------------------------------- /src/functionalTest/resources/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "test" 2 | -------------------------------------------------------------------------------- /src/integrationTest/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/core/GitTaggerSpec.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.core 2 | 3 | import io.kotest.core.spec.style.StringSpec 4 | import io.kotest.matchers.shouldBe 5 | import org.eclipse.jgit.api.Git 6 | import org.eclipse.jgit.revwalk.RevWalk 7 | import org.eclipse.jgit.transport.URIish 8 | import uk.co.developmentanddinosaurs.gradle.gitversioner.core.tag.GitTagger 9 | import uk.co.developmentanddinosaurs.gradle.gitversioner.core.tag.TaggerConfig 10 | import java.io.File 11 | 12 | class GitTaggerSpec : StringSpec({ 13 | 14 | val projectDir = File("build/tmp/integrationTest/local") 15 | val remoteDir = File("build/tmp/integrationTest/remote") 16 | 17 | lateinit var localGit: Git 18 | lateinit var remoteGit: Git 19 | 20 | fun addCommitToLocalRepository(git: Git) { 21 | git.commit().setSign(false).setAllowEmpty(true).setMessage("Commit\nCommit").call() 22 | } 23 | 24 | fun addRemoteAsLocalOrigin(git: Git) { 25 | git.remoteAdd().setName("origin").setUri(URIish(remoteDir.absolutePath)).call() 26 | } 27 | 28 | fun createRepository(folder: File): Git { 29 | Git.init().setDirectory(folder).call() 30 | return Git.open(File(folder.absolutePath + "/.git")) 31 | } 32 | 33 | fun createTagger( 34 | prefix: String = "v", 35 | useCommitMessage: Boolean = false, 36 | ) = GitTagger( 37 | projectDir, 38 | object : TaggerConfig { 39 | override val username = null 40 | override val password = null 41 | override val token = null 42 | override val strictHostChecking = false 43 | override val prefix = prefix 44 | override val useCommitMessage = useCommitMessage 45 | }, 46 | ) 47 | 48 | fun lastTag(git: Git) = RevWalk(git.repository).parseTag(git.tagList().call()[0].objectId) 49 | 50 | beforeTest { 51 | localGit = createRepository(projectDir) 52 | remoteGit = createRepository(remoteDir) 53 | addCommitToLocalRepository(localGit) 54 | addRemoteAsLocalOrigin(localGit) 55 | } 56 | 57 | afterTest { 58 | projectDir.deleteRecursively() 59 | remoteDir.deleteRecursively() 60 | } 61 | 62 | "Creates tag locally and pushes to remote repository" { 63 | val tagger = createTagger() 64 | 65 | tagger.tag("1.0.0") 66 | 67 | lastTag(localGit).tagName shouldBe "v1.0.0" 68 | lastTag(remoteGit).tagName shouldBe "v1.0.0" 69 | } 70 | "Creates overridden tag locally and pushes to remote repository" { 71 | val tagger = createTagger(prefix = "x") 72 | 73 | tagger.tag("1.0.0") 74 | 75 | lastTag(localGit).tagName shouldBe "x1.0.0" 76 | } 77 | "Creates tag with no message when not specified" { 78 | val tagger = createTagger() 79 | 80 | tagger.tag("1.0.0") 81 | 82 | lastTag(localGit).fullMessage shouldBe "" 83 | } 84 | "Creates tag with message as last commit message" { 85 | val tagger = createTagger(useCommitMessage = true) 86 | 87 | tagger.tag("1.0.0") 88 | 89 | lastTag(localGit).fullMessage shouldBe "Commit\nCommit" 90 | } 91 | }) 92 | -------------------------------------------------------------------------------- /src/integrationTest/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/core/VersionerSpec.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.core 2 | 3 | import io.kotest.core.spec.style.StringSpec 4 | import io.kotest.matchers.shouldBe 5 | import org.eclipse.jgit.api.Git 6 | import uk.co.developmentanddinosaurs.gradle.gitversioner.core.version.Versioner 7 | import uk.co.developmentanddinosaurs.gradle.gitversioner.core.version.VersionerConfig 8 | import java.io.File 9 | 10 | class VersionerSpec : StringSpec({ 11 | 12 | val projectDir = File("build/tmp/integrationTest/local") 13 | 14 | lateinit var git: Git 15 | 16 | fun createRepository(folder: File): Git { 17 | folder.mkdirs() 18 | Git.init().setDirectory(folder).call() 19 | return Git.open(File(folder.absolutePath + "/.git")) 20 | } 21 | 22 | fun calculateVersion( 23 | startFromMajor: Int = 0, 24 | startFromMinor: Int = 0, 25 | startFromPatch: Int = 0, 26 | matchMajor: String = "[major]", 27 | matchMinor: String = "[minor]", 28 | matchPatch: String = "[patch]", 29 | ) = Versioner(projectDir).version( 30 | object : VersionerConfig { 31 | override val startFromMajor = startFromMajor 32 | override val startFromMinor = startFromMinor 33 | override val startFromPatch = startFromPatch 34 | override val matchMajor = matchMajor 35 | override val matchMinor = matchMinor 36 | override val matchPatch = matchPatch 37 | override val pattern = "" 38 | }, 39 | ) 40 | 41 | fun createCommits( 42 | message: String, 43 | number: Int, 44 | ) { 45 | for (i in 1..number) { 46 | git.commit().setSign(false).setAllowEmpty(true).setMessage(message).call() 47 | } 48 | } 49 | 50 | fun givenRepositoryHasTypeCommitsNumbering( 51 | message: String, 52 | number: Int, 53 | ) { 54 | createCommits("[$message]", number) 55 | } 56 | 57 | beforeTest { 58 | git = createRepository(projectDir) 59 | } 60 | 61 | afterTest { 62 | projectDir.deleteRecursively() 63 | } 64 | 65 | "Increments major version for commit messages matching default major regex" { 66 | givenRepositoryHasTypeCommitsNumbering("major", 3) 67 | 68 | val version = calculateVersion() 69 | 70 | version.major shouldBe 3 71 | } 72 | "Increments minor version for commit messages matching default minor regex" { 73 | givenRepositoryHasTypeCommitsNumbering("minor", 2) 74 | 75 | val version = calculateVersion() 76 | 77 | version.minor shouldBe 2 78 | } 79 | "Increments patch version for commit messages matching default patch regex" { 80 | givenRepositoryHasTypeCommitsNumbering("patch", 1) 81 | 82 | val version = calculateVersion() 83 | 84 | version.patch shouldBe 1 85 | } 86 | "Increments commit version for commit messages matching not matching any regex" { 87 | givenRepositoryHasTypeCommitsNumbering("hello", 4) 88 | 89 | val version = calculateVersion() 90 | 91 | version.commit shouldBe 4 92 | } 93 | "Major version increment resets minor, patch, and commit versions" { 94 | givenRepositoryHasTypeCommitsNumbering("hello", 1) 95 | givenRepositoryHasTypeCommitsNumbering("patch", 1) 96 | givenRepositoryHasTypeCommitsNumbering("minor", 1) 97 | givenRepositoryHasTypeCommitsNumbering("major", 1) 98 | 99 | val version = calculateVersion() 100 | 101 | version.major shouldBe 1 102 | version.minor shouldBe 0 103 | version.patch shouldBe 0 104 | version.commit shouldBe 0 105 | } 106 | "Minor version increment resets patch and commit versions" { 107 | givenRepositoryHasTypeCommitsNumbering("hello", 1) 108 | givenRepositoryHasTypeCommitsNumbering("patch", 1) 109 | givenRepositoryHasTypeCommitsNumbering("minor", 1) 110 | 111 | val version = calculateVersion() 112 | 113 | version.minor shouldBe 1 114 | version.patch shouldBe 0 115 | version.commit shouldBe 0 116 | } 117 | "Patch version increment resets commit versions" { 118 | givenRepositoryHasTypeCommitsNumbering("hello", 1) 119 | givenRepositoryHasTypeCommitsNumbering("patch", 1) 120 | 121 | val version = calculateVersion() 122 | 123 | version.patch shouldBe 1 124 | version.commit shouldBe 0 125 | } 126 | "Commit version increment resets nothing" { 127 | givenRepositoryHasTypeCommitsNumbering("major", 1) 128 | givenRepositoryHasTypeCommitsNumbering("minor", 1) 129 | givenRepositoryHasTypeCommitsNumbering("patch", 1) 130 | givenRepositoryHasTypeCommitsNumbering("hello", 1) 131 | 132 | val version = calculateVersion() 133 | 134 | version.major shouldBe 1 135 | version.minor shouldBe 1 136 | version.patch shouldBe 1 137 | version.commit shouldBe 1 138 | } 139 | "Works even when there's loads of commits" { 140 | givenRepositoryHasTypeCommitsNumbering("major", 100) 141 | givenRepositoryHasTypeCommitsNumbering("minor", 100) 142 | givenRepositoryHasTypeCommitsNumbering("patch", 100) 143 | givenRepositoryHasTypeCommitsNumbering("hello", 100) 144 | 145 | val version = calculateVersion() 146 | 147 | version.major shouldBe 100 148 | version.minor shouldBe 100 149 | version.patch shouldBe 100 150 | version.commit shouldBe 100 151 | } 152 | "Increments major version from point specified in configuration" { 153 | givenRepositoryHasTypeCommitsNumbering("major", 1) 154 | 155 | val version = calculateVersion(startFromMajor = 1) 156 | 157 | version.major shouldBe 2 158 | } 159 | "Increments minor version from point specified in configuration" { 160 | givenRepositoryHasTypeCommitsNumbering("minor", 1) 161 | 162 | val version = calculateVersion(startFromMinor = 2) 163 | 164 | version.minor shouldBe 3 165 | } 166 | "Increments patch version from point specified in configuration" { 167 | givenRepositoryHasTypeCommitsNumbering("patch", 1) 168 | 169 | val version = calculateVersion(startFromPatch = 3) 170 | 171 | version.patch shouldBe 4 172 | } 173 | "Increments major version based on major match regex specified in configuration" { 174 | givenRepositoryHasTypeCommitsNumbering("trex", 1) 175 | 176 | val version = calculateVersion(matchMajor = "trex") 177 | 178 | version.major shouldBe 1 179 | } 180 | "Increments minor version based on minor match regex specified in configuration" { 181 | givenRepositoryHasTypeCommitsNumbering("stego", 1) 182 | 183 | val version = calculateVersion(matchMinor = "stego") 184 | 185 | version.minor shouldBe 1 186 | } 187 | "Increments patch version based on patch match regex specified in configuration" { 188 | givenRepositoryHasTypeCommitsNumbering("compy", 1) 189 | 190 | val version = calculateVersion(matchPatch = "compy") 191 | 192 | version.patch shouldBe 1 193 | } 194 | "Version includes current branch" { 195 | givenRepositoryHasTypeCommitsNumbering("hello", 1) 196 | 197 | val version = calculateVersion() 198 | 199 | version.branch shouldBe "master" 200 | } 201 | "Version includes commit hash from HEAD" { 202 | givenRepositoryHasTypeCommitsNumbering("hello", 1) 203 | 204 | val version = calculateVersion() 205 | 206 | version.hash shouldBe git.repository.findRef("HEAD").objectId.name 207 | } 208 | }) 209 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/VersionerPlugin.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner 2 | 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | import org.gradle.kotlin.dsl.create 6 | import org.gradle.kotlin.dsl.register 7 | import uk.co.developmentanddinosaurs.gradle.gitversioner.configuration.TaggerExtensionConfig 8 | import uk.co.developmentanddinosaurs.gradle.gitversioner.configuration.VersionerExtension 9 | import uk.co.developmentanddinosaurs.gradle.gitversioner.configuration.VersionerExtensionConfig 10 | import uk.co.developmentanddinosaurs.gradle.gitversioner.core.tag.GitTagger 11 | import uk.co.developmentanddinosaurs.gradle.gitversioner.core.version.Versioner 12 | import uk.co.developmentanddinosaurs.gradle.gitversioner.tasks.PrintVersionTask 13 | import uk.co.developmentanddinosaurs.gradle.gitversioner.tasks.TagVersionTask 14 | import java.io.File 15 | 16 | /** 17 | * A custom Gradle plugin that adds tasks related to semantic versioning via Git. 18 | */ 19 | class VersionerPlugin : Plugin { 20 | /** 21 | * Apply the plugin to the project. 22 | * 23 | * @param project the project to apply the plugin to 24 | */ 25 | override fun apply(project: Project) { 26 | val gitFolder = File("${project.rootDir}/.git") 27 | val versioner = Versioner(gitFolder) 28 | val extension = project.extensions.create("versioner", project, versioner) 29 | val gitTagger = GitTagger(gitFolder, TaggerExtensionConfig(extension)) 30 | val printVersionTask = project.tasks.register("printVersion") 31 | val tagVersionTask = project.tasks.register("tagVersion", gitTagger) 32 | project.afterEvaluate { 33 | if (extension.calculatedVersion == null) { 34 | val config = VersionerExtensionConfig(extension) 35 | val version = versioner.version(config).print(config.pattern) 36 | project.version = version 37 | } 38 | val projectVersion = project.version.toString() 39 | printVersionTask.get().version = projectVersion 40 | tagVersionTask.get().version = projectVersion 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/configuration/ConfigurationException.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.configuration 2 | 3 | /** 4 | * An exception that is thrown when there is an issue with the configuration for the versioner task. 5 | */ 6 | class ConfigurationException(message: String) : RuntimeException(message) 7 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/configuration/Match.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.configuration 2 | 3 | import org.gradle.api.Action 4 | 5 | /** 6 | * Configuration to specify the patterns to match against for version increments. 7 | */ 8 | class Match(action: Action) { 9 | /** 10 | * The pattern to match for major version increments. 11 | * 12 | * Default is "[major]". 13 | */ 14 | var major = "[major]" 15 | 16 | /** 17 | * The pattern to match for minor version increments. 18 | * 19 | * Default is "[minor]". 20 | */ 21 | var minor = "[minor]" 22 | 23 | /** 24 | * The pattern to match for patch version increments. 25 | * 26 | * Default is "[patch]". 27 | */ 28 | var patch = "[patch]" 29 | 30 | init { 31 | action.execute(this) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/configuration/Pattern.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.configuration 2 | 3 | import org.gradle.api.Action 4 | 5 | /** 6 | * Configuration to specify the pattern to use for versioning. 7 | */ 8 | class Pattern(action: Action) { 9 | /** 10 | * The pattern to use for versioning. 11 | * 12 | * Default is "%M.%m.%p(.%c)" 13 | */ 14 | var pattern = "%M.%m.%p(.%c)" 15 | 16 | init { 17 | action.execute(this) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/configuration/StartFrom.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.configuration 2 | 3 | import org.gradle.api.Action 4 | 5 | /** 6 | * Configuration to specify the version to start from. 7 | */ 8 | class StartFrom(action: Action) { 9 | /** 10 | * The major version to start from. 11 | * 12 | * Default is "0". 13 | */ 14 | var major = 0 15 | 16 | /** 17 | * The minor version to start from. 18 | * 19 | * Default is "0". 20 | */ 21 | var minor = 0 22 | 23 | /** 24 | * The patch version to start from. 25 | * 26 | * Default is "0". 27 | */ 28 | var patch = 0 29 | 30 | init { 31 | action.execute(this) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/configuration/Tag.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.configuration 2 | 3 | import org.gradle.api.Action 4 | 5 | /** 6 | * Configuration used for tagging. 7 | */ 8 | class Tag(action: Action) { 9 | /** 10 | * The prefix to use for the version tag. 11 | * 12 | * Default is "v". 13 | */ 14 | var prefix = "v" 15 | 16 | /** 17 | * Whether to use the commit message as the tag message. 18 | * 19 | * Default is "false". 20 | */ 21 | var useCommitMessage = false 22 | 23 | init { 24 | action.execute(this) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/configuration/TaggerExtensionConfig.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.configuration 2 | 3 | import uk.co.developmentanddinosaurs.gradle.gitversioner.core.tag.TaggerConfig 4 | 5 | /** 6 | * Implementation of [TaggerConfig] that uses a Gradle specific [VersionerExtension] to collect the configuration. 7 | */ 8 | class TaggerExtensionConfig(extension: VersionerExtension) : TaggerConfig { 9 | override val username by lazy { extension.git.authentication.https.username } 10 | override val password by lazy { extension.git.authentication.https.password } 11 | override val token by lazy { extension.git.authentication.https.token } 12 | override val strictHostChecking by lazy { extension.git.authentication.ssh.strictSsl } 13 | override val prefix by lazy { extension.tag.prefix } 14 | override val useCommitMessage by lazy { extension.tag.useCommitMessage } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/configuration/VersionerExtension.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.configuration 2 | 3 | import org.gradle.api.Action 4 | import org.gradle.api.Project 5 | import uk.co.developmentanddinosaurs.gradle.gitversioner.configuration.git.Git 6 | import uk.co.developmentanddinosaurs.gradle.gitversioner.core.version.Versioner 7 | import javax.inject.Inject 8 | 9 | /** 10 | * Collects configuration details in the Gradle context. 11 | * 12 | * This extension can be used in a gradle build file to provide required configuration, for example: 13 | * 14 | * ``` 15 | * versioner { 16 | * startFrom { 17 | * major = 1 18 | * minor = 1 19 | * patch = 1 20 | * } 21 | * match { 22 | * major = "trex" 23 | * minor = "stego" 24 | * patch = "compy" 25 | * } 26 | * tag { 27 | * prefix = "x" 28 | * useCommitMessage = true 29 | * } 30 | * git { 31 | * authentication { 32 | * ssh { 33 | * strictSsl = false 34 | * } 35 | * https { 36 | * username = "username" 37 | * password = "password" 38 | * token = "token" 39 | * } 40 | * } 41 | * } 42 | * pattern { 43 | * pattern = "%M.%m.%p(-%c)" 44 | * } 45 | * } 46 | * ``` 47 | */ 48 | open class VersionerExtension 49 | @Inject 50 | constructor( 51 | private val project: Project, 52 | private val versioner: Versioner, 53 | ) { 54 | /** 55 | * Holding field for calculated version. 56 | * 57 | * This field is set after the version is calculated, and can be interrogated to see whether we need to 58 | * perform version calculation later. 59 | */ 60 | var calculatedVersion: String? = null 61 | 62 | /** 63 | * Configures the versions to start versioning from, for when a project adopts the plugin after having versioned 64 | * a different way previously. For example: 65 | * 66 | * ``` 67 | * startFrom { 68 | * major = 1 69 | * minor = 1 70 | * patch = 1 71 | * } 72 | * ``` 73 | */ 74 | var startFrom = StartFrom { } 75 | 76 | /** 77 | * Configures the strings to match against when calculated versions. For example: 78 | * 79 | * ``` 80 | * match { 81 | * major = "trex" 82 | * minor = "stego" 83 | * patch = "compy" 84 | * } 85 | * ``` 86 | */ 87 | var match = Match { } 88 | 89 | /** 90 | * Configures the tagging behaviour. For example: 91 | * 92 | * ``` 93 | * tag { 94 | * prefix = "x" 95 | * useCommitMessage = true 96 | * } 97 | * ``` 98 | */ 99 | var tag = Tag { } 100 | 101 | /** 102 | * Configures the git behaviour. For example: 103 | * 104 | * ``` 105 | * git { 106 | * authentication { 107 | * ssh { 108 | * strictSsl = false 109 | * } 110 | * https { 111 | * username = "username" 112 | * password = "password" 113 | * token = "token" 114 | * } 115 | * } 116 | * } 117 | */ 118 | var git = Git { } 119 | 120 | /** 121 | * Configures the version pattern. For example: 122 | * 123 | * ``` 124 | * pattern { 125 | * pattern = "%M.%m.%p(-%c)" 126 | * } 127 | * ``` 128 | */ 129 | var pattern = Pattern { } 130 | 131 | /** 132 | * Sets the [StartFrom] configuration. 133 | */ 134 | fun startFrom(action: Action) { 135 | startFrom = StartFrom(action) 136 | } 137 | 138 | /** 139 | * Sets the [Match] configuration. 140 | */ 141 | fun match(action: Action) { 142 | match = Match(action) 143 | } 144 | 145 | /** 146 | * Sets the [Tag] configuration. 147 | */ 148 | fun tag(action: Action) { 149 | tag = Tag(action) 150 | } 151 | 152 | /** 153 | * Sets the [Git] configuration. 154 | */ 155 | fun git(action: Action) { 156 | git = Git(action) 157 | } 158 | 159 | /** 160 | * Sets the [Pattern] configuration. 161 | */ 162 | fun pattern(action: Action) { 163 | pattern = Pattern(action) 164 | } 165 | 166 | /** 167 | * Applies to versioning behaviour ahead of the usual time. 168 | * 169 | * This can be used to set the version ahead of time, for example: 170 | * 171 | * ``` 172 | * versioner.apply() 173 | * ``` 174 | * 175 | * This can be used where the version needs to be used during the configuration phase, before it would have been 176 | * calculated in the normal lifecycle. Consumers should not rely on this functionality and should prefer to defer 177 | * their versioning reliance until the end of the configuration phase, or the start of the execution phase. This 178 | * method can be used where that is not otherwise possible. 179 | */ 180 | fun apply() { 181 | val config = VersionerExtensionConfig(this) 182 | val version = versioner.version(config) 183 | val versionString = version.print(config.pattern) 184 | calculatedVersion = versionString 185 | project.version = versionString 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/configuration/VersionerExtensionConfig.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.configuration 2 | 3 | import uk.co.developmentanddinosaurs.gradle.gitversioner.core.version.VersionerConfig 4 | 5 | /** 6 | * Implementation of [VersionerConfig] that uses a Gradle specific [VersionerExtension] to collect the configuration. 7 | */ 8 | class VersionerExtensionConfig(extension: VersionerExtension) : VersionerConfig { 9 | override val startFromMajor by lazy { extension.startFrom.major } 10 | override val startFromMinor by lazy { extension.startFrom.minor } 11 | override val startFromPatch by lazy { extension.startFrom.patch } 12 | override val matchMajor by lazy { extension.match.major } 13 | override val matchMinor by lazy { extension.match.minor } 14 | override val matchPatch by lazy { extension.match.patch } 15 | override val pattern by lazy { extension.pattern.pattern } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/configuration/git/Authentication.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.configuration.git 2 | 3 | import groovy.lang.Closure 4 | import org.gradle.api.Action 5 | 6 | /** 7 | * Configuration to specify the authentication behaviour. 8 | */ 9 | class Authentication(action: Action) { 10 | /** 11 | * The SSH configuration to use for authentication. 12 | */ 13 | var ssh = Ssh(Action { }) 14 | 15 | /** 16 | * The HTTPS configuration to use for authentication. 17 | */ 18 | var https = 19 | Https(Action { }) 20 | 21 | init { 22 | action.execute(this) 23 | } 24 | 25 | fun ssh(action: Action) { 26 | ssh = Ssh(action) 27 | } 28 | 29 | fun ssh(closure: Closure) { 30 | closure.resolveStrategy = Closure.DELEGATE_FIRST 31 | closure.delegate = ssh 32 | closure.call() 33 | } 34 | 35 | fun https(action: Action) { 36 | https = Https(action) 37 | } 38 | 39 | fun https(closure: Closure) { 40 | closure.resolveStrategy = Closure.DELEGATE_FIRST 41 | closure.delegate = https 42 | closure.call() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/configuration/git/Git.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.configuration.git 2 | 3 | import groovy.lang.Closure 4 | import org.gradle.api.Action 5 | 6 | /** 7 | * Configuration to specify the Git behaviour. 8 | */ 9 | class Git(action: Action) { 10 | /** 11 | * The authentication configuration to use for Git. 12 | */ 13 | var authentication = 14 | Authentication(Action { }) 15 | 16 | init { 17 | action.execute(this) 18 | } 19 | 20 | fun authentication(action: Action) { 21 | authentication = 22 | Authentication( 23 | action, 24 | ) 25 | } 26 | 27 | fun authentication(closure: Closure) { 28 | closure.resolveStrategy = Closure.DELEGATE_FIRST 29 | closure.delegate = authentication 30 | closure.call() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/configuration/git/Https.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.configuration.git 2 | 3 | import org.gradle.api.Action 4 | 5 | /** 6 | * Configuration to specify the HTTPS behaviour. 7 | */ 8 | class Https(action: Action) { 9 | /** 10 | * The username to authenticate with git. 11 | */ 12 | var username: String? = null 13 | 14 | /** 15 | * The password to authenticate with git. 16 | */ 17 | var password: String? = null 18 | 19 | /** 20 | * The token to authenticate with git. 21 | */ 22 | var token: String? = null 23 | 24 | init { 25 | action.execute(this) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/configuration/git/Ssh.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.configuration.git 2 | 3 | import org.gradle.api.Action 4 | 5 | /** 6 | * Configuration to specify the SSH behaviour. 7 | */ 8 | class Ssh(action: Action) { 9 | /** 10 | * Whether to verify the host public key. 11 | * 12 | * Default is "true" 13 | */ 14 | var strictSsl = true 15 | 16 | init { 17 | action.execute(this) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/core/tag/GitTagger.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.core.tag 2 | 3 | import com.jcraft.jsch.JSch 4 | import org.eclipse.jgit.api.Git 5 | import org.eclipse.jgit.transport.CredentialsProvider 6 | import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider 7 | import java.io.File 8 | 9 | /** 10 | * Creates and pushes git tags based on the tagging config provider. 11 | */ 12 | class GitTagger(private val gitFolder: File, private val config: TaggerConfig) { 13 | /** 14 | * Creates a git tag and pushes it to the remote repository. 15 | * 16 | * @param version the version to tag 17 | */ 18 | fun tag(version: String) { 19 | configureHostChecking(config) 20 | val credentialsProvider = createCredentialsProvider(config) 21 | val prefixedVersion = "${config.prefix}$version" 22 | val git = Git.open(gitFolder) 23 | val tagCommand = git.tag().setName(prefixedVersion) 24 | if (config.useCommitMessage) { 25 | tagCommand.setMessage(getLastCommitMessage(git)) 26 | } 27 | tagCommand.call() 28 | git.push().add(prefixedVersion).setCredentialsProvider(credentialsProvider).call() 29 | } 30 | 31 | private fun getLastCommitMessage(git: Git) = git.log().setMaxCount(1).call().iterator().next().fullMessage 32 | 33 | private fun configureHostChecking(config: TaggerConfig) { 34 | if (config.strictHostChecking) { 35 | JSch.setConfig("StrictHostKeyChecking", "yes") 36 | } else { 37 | JSch.setConfig("StrictHostKeyChecking", "no") 38 | } 39 | } 40 | 41 | private fun createCredentialsProvider(config: TaggerConfig): CredentialsProvider? { 42 | with(config) { 43 | return when { 44 | username != null -> UsernamePasswordCredentialsProvider(username, password) 45 | token != null -> UsernamePasswordCredentialsProvider(token, "") 46 | else -> CredentialsProvider.getDefault() 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/core/tag/TaggerConfig.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.core.tag 2 | 3 | /** 4 | * Configuration required for version tagging. 5 | */ 6 | interface TaggerConfig { 7 | /** 8 | * The git username to authenticate the tagging action with. 9 | * 10 | * Required when using username/password authentication. 11 | */ 12 | val username: String? 13 | 14 | /** 15 | * The git password to use for tagging. 16 | * 17 | * Required when using username/password authentication. 18 | */ 19 | val password: String? 20 | 21 | /** 22 | * The git token to use for tagging. 23 | * 24 | * Required when using token based authentication. 25 | */ 26 | val token: String? 27 | 28 | /** 29 | * Whether to perform verification of the specified host public key. 30 | */ 31 | val strictHostChecking: Boolean 32 | 33 | /** 34 | * Tagging prefix to use before the version number, e.g. 'v'. 35 | */ 36 | val prefix: String 37 | 38 | /** 39 | * Whether to use the full commit message of the latest commit as the tag message. 40 | */ 41 | val useCommitMessage: Boolean 42 | } 43 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/core/version/Version.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.core.version 2 | 3 | /** 4 | * A calculated version based on the provided version details. 5 | */ 6 | data class Version( 7 | val major: Int, 8 | val minor: Int, 9 | val patch: Int, 10 | val commit: Int, 11 | val branch: String, 12 | val hash: String, 13 | ) { 14 | /** 15 | * Print the version based on the provided pattern. 16 | * 17 | * @param pattern The pattern to use to print the version. 18 | * @return The version string. 19 | */ 20 | fun print(pattern: String): String { 21 | val filledVersion = 22 | pattern 23 | .replace("%M", major.toString()) 24 | .replace("%m", minor.toString()) 25 | .replace("%p", patch.toString()) 26 | .replace("%c", commit.toString()) 27 | .replace("%b", branch) 28 | .replace("%H", hash) 29 | .replace("%h", hash.substring(0, 7)) 30 | return if (commit != 0) { 31 | removeParentheses(filledVersion) 32 | } else { 33 | removeCommitConditionals(filledVersion) 34 | } 35 | } 36 | 37 | private fun removeCommitConditionals(version: String) = version.replace(Regex("\\(.*\\)"), "") 38 | 39 | private fun removeParentheses(version: String) = version.replace("(", "").replace(")", "") 40 | } 41 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/core/version/Versioner.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.core.version 2 | 3 | import org.eclipse.jgit.api.Git 4 | import java.io.File 5 | 6 | /** 7 | * Calculates the full version of a project based on the git history provided. 8 | */ 9 | class Versioner(private val gitFolder: File) { 10 | /** 11 | * Calculate the version for the provided configuration. 12 | * 13 | * @param config the [VersionerConfig] to use for calculating the version 14 | * @return the [Version] calculated using the configuration and history 15 | */ 16 | fun version(config: VersionerConfig): Version { 17 | var major = config.startFromMajor 18 | var minor = config.startFromMinor 19 | var patch = config.startFromPatch 20 | var commit = 0 21 | 22 | val git = Git.open(gitFolder) 23 | 24 | val branch = git.repository.branch 25 | val hash = git.repository.findRef("HEAD").objectId.name 26 | 27 | val all = git.log().call() 28 | all.reversed().forEach { 29 | when { 30 | it.fullMessage.contains(config.matchMajor) -> { 31 | major++ 32 | minor = 0 33 | patch = 0 34 | commit = 0 35 | } 36 | it.fullMessage.contains(config.matchMinor) -> { 37 | minor++ 38 | patch = 0 39 | commit = 0 40 | } 41 | it.fullMessage.contains(config.matchPatch) -> { 42 | patch++ 43 | commit = 0 44 | } 45 | else -> commit++ 46 | } 47 | } 48 | return Version(major, minor, patch, commit, branch, hash) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/core/version/VersionerConfig.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.core.version 2 | 3 | /** 4 | * Configuration required for versioning. 5 | */ 6 | interface VersionerConfig { 7 | /** 8 | * The integer to start counting from for major version increments. 9 | */ 10 | val startFromMajor: Int 11 | 12 | /** 13 | * The integer to start counting from for minor version increments. 14 | */ 15 | val startFromMinor: Int 16 | 17 | /** 18 | * The integer to start counting from for patch version increments. 19 | */ 20 | val startFromPatch: Int 21 | 22 | /** 23 | * The string to match against for major version increments. 24 | */ 25 | val matchMajor: String 26 | 27 | /** 28 | * The string to match against for minor version increments. 29 | */ 30 | val matchMinor: String 31 | 32 | /** 33 | * The string to match against for patch version increments. 34 | */ 35 | val matchPatch: String 36 | 37 | /** 38 | * The string pattern to use for versioning. 39 | */ 40 | val pattern: String 41 | } 42 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/tasks/PrintVersionTask.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.tasks 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.tasks.Input 5 | import org.gradle.api.tasks.TaskAction 6 | 7 | /** 8 | * Task for performing version printing via `printVersion` command. 9 | */ 10 | abstract class PrintVersionTask : DefaultTask() { 11 | init { 12 | group = "versioning" 13 | description = "Print the calculated version" 14 | } 15 | 16 | /** 17 | * The version to tag when running the action. 18 | */ 19 | @get:Input 20 | lateinit var version: String 21 | 22 | /** 23 | * Action to run when the `printVersion` task is executed. 24 | */ 25 | @TaskAction 26 | fun printVersion() { 27 | println(version) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/tasks/TagVersionTask.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.tasks 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.tasks.Input 5 | import org.gradle.api.tasks.TaskAction 6 | import uk.co.developmentanddinosaurs.gradle.gitversioner.core.tag.GitTagger 7 | import javax.inject.Inject 8 | 9 | /** 10 | * Task for performing version tagging via `tagVersion` command. 11 | */ 12 | abstract class TagVersionTask 13 | @Inject 14 | constructor(private val tagger: GitTagger) : DefaultTask() { 15 | init { 16 | group = "versioning" 17 | description = "Tag the calculated version" 18 | } 19 | 20 | /** 21 | * The version to tag when running the action. 22 | */ 23 | @get:Input 24 | lateinit var version: String 25 | 26 | /** 27 | * Action to run when the `tagVersion` task is executed. 28 | */ 29 | @TaskAction 30 | fun tagVersion() { 31 | tagger.tag(version) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/FileExtensions.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner 2 | 3 | import java.io.File 4 | import java.io.InputStream 5 | 6 | fun File.withContents(inputStream: InputStream): File { 7 | this.writeBytes(inputStream.readBytes()) 8 | return this 9 | } 10 | -------------------------------------------------------------------------------- /src/test/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/configuration/ConfigurationExceptionSpec.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.configuration 2 | 3 | import io.kotest.core.spec.style.StringSpec 4 | import io.kotest.matchers.shouldBe 5 | 6 | class ConfigurationExceptionSpec : StringSpec() { 7 | init { 8 | "Has correct message" { 9 | val message = "my message" 10 | try { 11 | throw ConfigurationException(message) 12 | } catch (e: ConfigurationException) { 13 | e.message shouldBe message 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/configuration/VersionerExtensionTest.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.configuration 2 | 3 | import io.kotest.core.spec.style.FunSpec 4 | import io.kotest.matchers.shouldBe 5 | import io.mockk.every 6 | import io.mockk.mockk 7 | import io.mockk.slot 8 | import org.gradle.api.Project 9 | import uk.co.developmentanddinosaurs.gradle.gitversioner.core.version.Version 10 | import uk.co.developmentanddinosaurs.gradle.gitversioner.core.version.Versioner 11 | 12 | class VersionerExtensionTest : FunSpec({ 13 | 14 | val project = mockk() 15 | val versioner = mockk() 16 | 17 | val extension = VersionerExtension(project, versioner) 18 | 19 | test("Can apply version to project") { 20 | val versionSlot = slot() 21 | every { project.version = capture(versionSlot) } answers {} 22 | every { versioner.version(any()) } returns Version(1, 0, 0, 0, "branch", "hash123") 23 | 24 | extension.apply() 25 | 26 | versionSlot.captured shouldBe "1.0.0" 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /src/test/kotlin/uk/co/developmentanddinosaurs/gradle/gitversioner/version/VersionSpec.kt: -------------------------------------------------------------------------------- 1 | package uk.co.developmentanddinosaurs.gradle.gitversioner.version 2 | 3 | import io.kotest.core.spec.style.StringSpec 4 | import io.kotest.data.forAll 5 | import io.kotest.data.row 6 | import io.kotest.matchers.shouldBe 7 | import uk.co.developmentanddinosaurs.gradle.gitversioner.core.version.Version 8 | 9 | class VersionSpec : StringSpec() { 10 | init { 11 | val versionWithCommit = Version(1, 2, 3, 4, "mybranch", "myhash123") 12 | val versionWithoutCommit = Version(1, 2, 3, 0, "mybranch", "myhash123") 13 | 14 | "prints version correctly according to pattern" { 15 | forAll( 16 | row(versionWithCommit, "%M.%m.%p.%c", "1.2.3.4"), 17 | row(versionWithCommit, "%M.%m.%p-%c", "1.2.3-4"), 18 | row(versionWithCommit, "%M.%m.%p-%H", "1.2.3-myhash123"), 19 | row(versionWithCommit, "%M.%m.%p-%h", "1.2.3-myhash1"), 20 | row(versionWithCommit, "%M.%m.%p-%b", "1.2.3-mybranch"), 21 | row(versionWithCommit, "%M.%m.%p(-%c)", "1.2.3-4"), 22 | row(versionWithoutCommit, "%M.%m.%p(-%b)", "1.2.3"), 23 | row(versionWithCommit, "%M.%m.%p(-SNAPSHOT)", "1.2.3-SNAPSHOT"), 24 | row(versionWithoutCommit, "%M.%m.%p(-SNAPSHOT)", "1.2.3"), 25 | ) { version, pattern, output -> 26 | val print = version.print(pattern) 27 | print shouldBe output 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/resources/configured-build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'uk.co.developmentanddinosaurs.git-versioner' 3 | } 4 | 5 | versioner { 6 | startFrom { 7 | major = 1 8 | minor = 1 9 | patch = 1 10 | } 11 | match { 12 | major = "major" 13 | minor = "minor" 14 | patch = "patch" 15 | } 16 | tag { 17 | prefix = "x" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/resources/default-build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'uk.co.developmentanddinosaurs.git-versioner' 3 | } 4 | --------------------------------------------------------------------------------