├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── gradle ├── HEADER └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src ├── gh-pages └── index.html ├── main ├── ghpages │ └── index.html ├── groovy │ └── org │ │ └── ajoberstar │ │ └── gradle │ │ ├── git │ │ ├── auth │ │ │ └── BasicPasswordCredentials.java │ │ ├── base │ │ │ └── GrgitPlugin.groovy │ │ ├── ghpages │ │ │ ├── GithubPagesPlugin.groovy │ │ │ └── GithubPagesPluginExtension.groovy │ │ └── release │ │ │ ├── base │ │ │ ├── BaseReleasePlugin.groovy │ │ │ ├── DefaultVersionStrategy.groovy │ │ │ ├── ReleasePluginExtension.groovy │ │ │ ├── ReleaseVersion.groovy │ │ │ ├── TagStrategy.groovy │ │ │ ├── VersionStrategy.groovy │ │ │ └── package-info.groovy │ │ │ ├── experimental │ │ │ └── ExperimentalReleasePlugin.groovy │ │ │ ├── opinion │ │ │ ├── OpinionReleasePlugin.groovy │ │ │ ├── Strategies.groovy │ │ │ └── package-info.groovy │ │ │ └── semver │ │ │ ├── ChangeScope.groovy │ │ │ ├── NearestVersion.groovy │ │ │ ├── NearestVersionLocator.groovy │ │ │ ├── PartialSemVerStrategy.groovy │ │ │ ├── RebuildVersionStrategy.groovy │ │ │ ├── SemVerStrategy.groovy │ │ │ ├── SemVerStrategyState.groovy │ │ │ ├── StrategyUtil.groovy │ │ │ └── package-info.groovy │ │ └── util │ │ └── ObjectUtil.java └── resources │ └── META-INF │ └── gradle-plugins │ ├── org.ajoberstar.github-pages.properties │ ├── org.ajoberstar.grgit.properties │ ├── org.ajoberstar.release-base.properties │ ├── org.ajoberstar.release-experimental.properties │ └── org.ajoberstar.release-opinion.properties └── test └── groovy └── org └── ajoberstar └── gradle └── git ├── ghpages ├── GithubPagesPluginExtensionSpec.groovy └── GithubPagesPluginSpec.groovy └── release ├── base ├── BaseReleasePluginSpec.groovy ├── ReleasePluginExtensionSpec.groovy └── TagStrategySpec.groovy ├── opinion ├── OpinionReleasePluginSpec.groovy └── StrategiesSpec.groovy └── semver ├── NearestVersionLocatorSpec.groovy ├── RebuildVersionStrategySpec.groovy ├── SemVerStrategySpec.groovy └── StrategyUtilSpec.groovy /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # defaults 7 | [*] 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | charset = utf-8 11 | indent_style = space 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | .settings 4 | .project 5 | .classpath 6 | bin 7 | *~ 8 | *.iml 9 | *.iws 10 | *.ipr 11 | .idea 12 | out 13 | *.sublime-* 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false 3 | install: "" 4 | script: ./gradlew clean check 5 | jdk: 6 | - oraclejdk8 7 | cache: 8 | directories: 9 | - $HOME/.gradle/caches 10 | - $HOME/.gradle/wrapper 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 16 | 17 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 18 | 19 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 22 | 23 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 24 | 25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 26 | 27 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 28 | 29 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 30 | 31 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 32 | 33 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 34 | 35 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 36 | 37 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 38 | You must cause any modified files to carry prominent notices stating that You changed the files; and 39 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 40 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 41 | 42 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 43 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 44 | 45 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 46 | 47 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 48 | 49 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 50 | 51 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 52 | 53 | END OF TERMS AND CONDITIONS 54 | 55 | APPENDIX: How to apply the Apache License to your work 56 | To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. 57 | 58 | Copyright [yyyy] [name of copyright owner] 59 | 60 | Licensed under the Apache License, Version 2.0 (the "License"); 61 | you may not use this file except in compliance with the License. 62 | You may obtain a copy of the License at 63 | 64 | http://www.apache.org/licenses/LICENSE-2.0 65 | 66 | Unless required by applicable law or agreed to in writing, software 67 | distributed under the License is distributed on an "AS IS" BASIS, 68 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 69 | See the License for the specific language governing permissions and 70 | limitations under the License. 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gradle-git 2 | 3 | [![Bintray](https://img.shields.io/bintray/v/ajoberstar/maven/gradle-git.svg?style=flat-square)](https://bintray.com/ajoberstar/maven/gradle-git/_latestVersion) 4 | [![Travis](https://img.shields.io/travis/ajoberstar/gradle-git.svg?style=flat-square)](https://travis-ci.org/ajoberstar/gradle-git) 5 | [![GitHub license](https://img.shields.io/github/license/ajoberstar/gradle-git.svg?style=flat-square)](https://github.com/ajoberstar/gradle-git/blob/master/LICENSE) 6 | 7 | ## Project Status 8 | 9 | gradle-git has been around since 2012 and has evolved quite a bit from the original release. In order to continue to evolve these features, this project is being broken up into multiple repositories. As such: 10 | 11 | - gradle-git will no longer be maintained 12 | 13 | | feature | replacement | comments | 14 | |---------|-------------|----------| 15 | | `org.ajoberstar.grgit` | [grgit](https://github.com/ajoberstar/grgit) | Grgit has been an independent project since 2013 and has been stable for quite a while. Version 2.0 removed some deprecated features, but otherwise is fully compatible with existing usage. It also integrates the `org.ajoberstar.grgit` plugin directly into the project. | 16 | | `org.ajoberstar.github-pages` | [gradle-git-publish](https://github.com/ajoberstar/gradle-git-publish) | `org.ajoberstar.git-publish` is a more robust version of the old plugin. It is functionally equivalent (or better), but does require porting configuration over as noted in the README. | 17 | | `org.ajoberstar.release-*` | [reckon](https://github.com/ajoberstar/reckon) | Reckon focuses solely on determining your project version (and assisting with tagging and pushing that tag). It provides an opinionated model of how to apply [semantic versioning](http://semver.org), with more finite configuration options. | 18 | | `org.ajoberstar.release-*` | [nebula-release](https://github.com/nebula-plugins/nebula-release-plugin) | If reckon doesn't suit your needs, nebula-release has forked the gradle-git release plugin and can serve as a replacement for this. | 19 | 20 | ## Why do you care? 21 | 22 | Git is immensely popular and being able to interact with it as part of a build process can be very valuable 23 | to provide a more powerful and consistent result. 24 | 25 | ## What is it? 26 | 27 | gradle-git is a set of [Gradle](http://gradle.org) plugins: 28 | 29 | * `org.ajoberstar.grgit` - provides a `Grgit` instance, allowing interaction with the Git repository 30 | the Gradle project is contained in 31 | * `org.ajoberstar.github-pages` - publishes files to the `gh-pages` branch of a Github repository 32 | * `org.ajoberstar.release-base` - general structure for inferring a project version and releasing it 33 | * `org.ajoberstar.release-opinion` - opinionated defaults for `org.ajoberstar.release-base` 34 | 35 | See [Grgit](https://github.com/ajoberstar/grgit) for details on the Git library used underneath, including 36 | configuration for authentication. 37 | 38 | ## Usage 39 | 40 | **NOTE:** gradle-git modules require Java 7 (or higher). 41 | 42 | * [Release Notes](https://github.com/ajoberstar/gradle-git/releases) 43 | * [Wiki](https://github.com/ajoberstar/gradle-git/wiki) 44 | * [Javadoc](http://ajoberstar.org/gradle-git/docs/javadoc) 45 | * [Groovydoc](http://ajoberstar.org/gradle-git/docs/groovydoc) 46 | 47 | ## Questions, Bugs, and Features 48 | 49 | gradle-git is not maintained anymore. See the _Project Status_ section above for details. 50 | 51 | ## Contributing 52 | 53 | gradle-git is not maintained anymore. See the _Project Status_ section above for details. 54 | 55 | ## Acknowledgements 56 | 57 | Thanks to all of the [contributors](https://github.com/ajoberstar/gradle-git/graphs/contributors). 58 | 59 | Credit goes to [Peter Ledbrook](https://github.com/pledbrook) for the initial 60 | idea for the `org.ajoberstar.github-pages` plugin. 61 | 62 | Thanks to [Zafar Khaja](https://github.com/zafarkhaja) for the very helpful 63 | [java-semver](https://github.com/zafarkhaja/jsemver) library. 64 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'groovy' 3 | id 'maven-publish' 4 | id 'com.jfrog.bintray' version '1.1' 5 | id 'org.ajoberstar.defaults' version '0.5.3' 6 | } 7 | 8 | group = 'org.ajoberstar' 9 | description = 'Git plugins for Gradle.' 10 | 11 | defaults { 12 | id = 'ajoberstar' 13 | 14 | bintrayRepo = 'maven' 15 | bintrayPkg = 'gradle-git' 16 | bintrayLabels = ['gradle', 'git', 'semver'] 17 | 18 | developers = [ 19 | [id: 'ajoberstar', name: 'Andrew Oberstar', email: 'andrew@ajoberstar.org'] 20 | ] 21 | 22 | copyrightYears = '2012-2017' 23 | } 24 | 25 | sourceCompatibility = '1.7' 26 | 27 | configurations { 28 | // use local groovy 29 | all*.exclude group: 'org.codehaus.groovy' 30 | } 31 | 32 | repositories { 33 | jcenter() 34 | } 35 | 36 | dependencies { 37 | // groovy 38 | compile localGroovy() 39 | 40 | // gradle api 41 | compile gradleApi() 42 | 43 | // grgit 44 | compile 'org.ajoberstar:grgit:1.9.3' 45 | 46 | // semver 47 | compile 'com.github.zafarkhaja:java-semver:0.9.0' 48 | 49 | // testing 50 | testCompile 'org.spockframework:spock-core:1.0-groovy-2.3' 51 | testRuntime 'cglib:cglib-nodep:3.1' 52 | } 53 | 54 | wrapper { 55 | gradleVersion = '2.1' 56 | } 57 | -------------------------------------------------------------------------------- /gradle/HEADER: -------------------------------------------------------------------------------- 1 | Copyright ${year} the original author or authors. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoberstar/gradle-git/03b08b9f8dd2b2ff0ee9228906a6e9781b85ec79/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Oct 19 16:00:40 CDT 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 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 %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="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 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /src/gh-pages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/ghpages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gradle-Git - Git plugins for Gradle 6 | 29 | 30 | 31 | Fork me on GitHub 32 |
33 |
34 | 35 | 36 |
37 | 38 |

Gradle-Git by ajoberstar

39 | 40 |
41 | Git plugins for Gradle 42 |
43 | 44 |

Documentation

45 | 50 | 51 |

License

52 |

Apache License v2

53 | 54 |

Authors

55 | 58 | 59 |

Download

60 |

61 | You can download this project in either 62 | zip or 63 | tar formats. 64 |

65 |

You can also clone the project with Git 66 | by running: 67 |

$ git clone git://github.com/ajoberstar/gradle-git
68 |

69 | 70 | 73 | 74 |
75 | 79 | 85 | 86 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/auth/BasicPasswordCredentials.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.auth; 17 | 18 | import java.io.Serializable; 19 | 20 | import org.ajoberstar.grgit.Credentials; 21 | 22 | import org.gradle.api.artifacts.repositories.PasswordCredentials; 23 | 24 | /** 25 | * Basic implementation of {@link PasswordCredentials}. 26 | * @since 0.1.0 27 | */ 28 | public class BasicPasswordCredentials implements PasswordCredentials, Serializable { 29 | private static final long serialVersionUID = 1L; 30 | private String username; 31 | private String password; 32 | 33 | /** 34 | * Constructs credentials with {@code null} username and password. 35 | */ 36 | public BasicPasswordCredentials() { 37 | this(null, null); 38 | } 39 | 40 | /** 41 | * Constructs credentials with the given arguments. 42 | * @param username the username to set 43 | * @param password the password to set 44 | */ 45 | public BasicPasswordCredentials(String username, String password) { 46 | this.username = username; 47 | this.password = password; 48 | } 49 | 50 | /** 51 | * {@inheritDoc} 52 | */ 53 | @Override 54 | public String getUsername() { 55 | return username; 56 | } 57 | 58 | /** 59 | * {@inheritDoc} 60 | */ 61 | @Override 62 | public void setUsername(String username) { 63 | this.username = username; 64 | } 65 | 66 | /** 67 | * {@inheritDoc} 68 | */ 69 | @Override 70 | public String getPassword() { 71 | return password; 72 | } 73 | 74 | /** 75 | * {@inheritDoc} 76 | */ 77 | @Override 78 | public void setPassword(String password) { 79 | this.password = password; 80 | } 81 | 82 | /** 83 | * Converts to credentials for use in Grgit. 84 | * @return {@code null} if both username and password are {@code null}, 85 | * otherwise returns credentials in Grgit format. 86 | */ 87 | public Credentials toGrgit() { 88 | if (username != null && password != null) { 89 | return new Credentials(username, password); 90 | } else { 91 | return null; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/base/GrgitPlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.base 17 | 18 | import org.ajoberstar.grgit.Grgit 19 | import org.ajoberstar.grgit.exception.GrgitException 20 | import org.eclipse.jgit.errors.RepositoryNotFoundException 21 | import org.gradle.api.Plugin 22 | import org.gradle.api.Project 23 | 24 | /** 25 | * Plugin adding a {@code grgit} property to all projects 26 | * that searches for a Git repo from the root project's 27 | * directory. 28 | * @since 1.2.0 29 | */ 30 | class GrgitPlugin implements Plugin { 31 | @Override 32 | void apply(Project project) { 33 | try { 34 | Grgit grgit = Grgit.open(currentDir: project.rootProject.rootDir) 35 | project.rootProject.allprojects { prj -> 36 | project.ext.grgit = grgit 37 | } 38 | } catch (RepositoryNotFoundException | GrgitException ignored) { 39 | // not a git repo or invalid/corrupt 40 | project.logger.warn 'No git repository found. Build may fail with NPE.' 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/ghpages/GithubPagesPlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.ghpages 17 | 18 | import org.ajoberstar.grgit.Grgit 19 | import org.ajoberstar.grgit.operation.ResetOp 20 | import org.ajoberstar.grgit.exception.GrgitException 21 | import org.eclipse.jgit.errors.RepositoryNotFoundException 22 | import org.gradle.api.Plugin 23 | import org.gradle.api.Project 24 | import org.gradle.api.Task 25 | import org.gradle.api.tasks.Copy 26 | 27 | /** 28 | * Plugin to enable publishing to gh-pages branch of Github. 29 | * @since 0.1.0 30 | */ 31 | class GithubPagesPlugin implements Plugin { 32 | static final String PREPARE_TASK_NAME = 'prepareGhPages' 33 | static final String PUBLISH_TASK_NAME = 'publishGhPages' 34 | 35 | /** 36 | * Applies the plugin to the given project. 37 | * @param project the project 38 | */ 39 | void apply(Project project) { 40 | project.logger.warn('org.ajoberstar.github-pages is deprecated will be removed in gradle-git 2.0.0. Users should migrate to org.ajoberstar.git-publish (https://github.com/ajoberstar/gradle-git-publish).') 41 | GithubPagesPluginExtension extension = project.extensions.create('githubPages', GithubPagesPluginExtension, project) 42 | configureTasks(project, extension) 43 | } 44 | 45 | /** 46 | * Configures the tasks to publish to gh-pages. 47 | * @param project the project to configure 48 | * @param extension the plugin extension 49 | */ 50 | private void configureTasks(final Project project, final GithubPagesPluginExtension extension) { 51 | Task prepare = createPrepareTask(project, extension) 52 | Task publish = createPublishTask(project, extension) 53 | publish.dependsOn(prepare) 54 | } 55 | 56 | private Task createPrepareTask(Project project, GithubPagesPluginExtension extension) { 57 | Task task = project.tasks.create(PREPARE_TASK_NAME, Copy) 58 | task.with { 59 | description = 'Prepare the gh-pages changes locally' 60 | with extension.pages.realSpec 61 | into { extension.workingDir } 62 | doFirst { 63 | def repo = repo(project, extension) 64 | if (extension.deleteExistingFiles) { 65 | def relDestDir = extension.pages.relativeDestinationDir 66 | def targetDir = new File(extension.workingDir, relDestDir) 67 | def filesList = targetDir.list { dir, name -> !name.equals('.git') } 68 | if(filesList) { 69 | def removePatterns = filesList 70 | if(relDestDir && relDestDir != '.') { 71 | removePatterns = filesList.collect {name -> "$relDestDir/$name"} 72 | } 73 | repo.remove(patterns: removePatterns) 74 | } 75 | } 76 | } 77 | doLast { 78 | def repo = repo(project, extension) 79 | repo.with { 80 | add(patterns: ['.']) 81 | if (status().clean) { 82 | project.logger.warn 'Nothing to commit, skipping publish.' 83 | } else { 84 | commit(message: extension.commitMessage) 85 | } 86 | } 87 | } 88 | } 89 | return task 90 | } 91 | 92 | private Task createPublishTask(Project project, GithubPagesPluginExtension extension) { 93 | return project.tasks.create(PUBLISH_TASK_NAME) { 94 | description = 'Publishes all gh-pages changes to Github' 95 | group = 'publishing' 96 | // only push if there are commits to push 97 | onlyIf { 98 | def repo = repo(project, extension) 99 | def status = repo.branch.status(name: repo.branch.current) 100 | status.aheadCount > 0 101 | } 102 | doLast { 103 | repo(project, extension).push() 104 | } 105 | } 106 | } 107 | 108 | private Grgit repo(Project project, GithubPagesPluginExtension extension) { 109 | if (extension.ext.has('repo')) { 110 | return extension.ext.repo 111 | } 112 | def repo = null 113 | try { 114 | // attempt to reuse existing repository 115 | repo = Grgit.open(dir: extension.workingDir) 116 | if (extension.repoUri == repo.remote.list().find { it.name == 'origin' }?.url && 117 | repo.branch.current.name == extension.targetBranch) { 118 | repo.clean(directories: true, ignore: false) 119 | repo.fetch() 120 | repo.reset(commit: 'origin/' + extension.targetBranch, mode: ResetOp.Mode.HARD) 121 | } 122 | else { 123 | project.logger.warn('Found a git repository at workingDir, but it does not match configuration. A fresh clone will be used.') 124 | repo.close() 125 | repo = null 126 | } 127 | } catch (RepositoryNotFoundException ignored) { 128 | // not a git repo 129 | } catch (GrgitException ignored) { 130 | // invalid/corrup git repo 131 | } 132 | 133 | if (!repo) { 134 | extension.workingDir.deleteDir() 135 | repo = Grgit.clone( 136 | uri: extension.repoUri, 137 | refToCheckout: extension.targetBranch, 138 | dir: extension.workingDir, 139 | credentials: extension.credentials?.toGrgit() 140 | ) 141 | 142 | // check if on the correct branch, which implies it doesn't exist 143 | if (repo.branch.current.name != extension.targetBranch) { 144 | repo.checkout(branch: extension.targetBranch, orphan: true) 145 | // need to wipe out the current files 146 | extension.deleteExistingFiles = true 147 | } 148 | } 149 | extension.ext.repo = repo 150 | return repo 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/ghpages/GithubPagesPluginExtension.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.ghpages 17 | 18 | import org.ajoberstar.grgit.Grgit 19 | import org.ajoberstar.grgit.exception.GrgitException 20 | 21 | import org.ajoberstar.gradle.git.auth.BasicPasswordCredentials 22 | import org.ajoberstar.gradle.util.ObjectUtil 23 | 24 | import org.gradle.api.Project 25 | import org.gradle.api.artifacts.repositories.AuthenticationSupported 26 | import org.gradle.api.artifacts.repositories.PasswordCredentials 27 | import org.gradle.api.file.CopySpec 28 | import org.gradle.util.ConfigureUtil 29 | 30 | /** 31 | * Extension for gh-pages specific properties. 32 | * @since 0.1.0 33 | */ 34 | class GithubPagesPluginExtension implements AuthenticationSupported { 35 | private final Project project 36 | PasswordCredentials credentials = new BasicPasswordCredentials() 37 | 38 | /** 39 | * The URI of the Github repository. 40 | */ 41 | Object repoUri 42 | 43 | /** 44 | * The branch of the Github repository to push to. 45 | * Defaults to {@code gh-pages} 46 | */ 47 | Object targetBranch = 'gh-pages' 48 | 49 | /** 50 | * The distribution of files to put in gh-pages. Defaults 51 | * to including {@code src/main/ghpages}. 52 | */ 53 | final CopySpec pages 54 | 55 | /** 56 | * The path to put the github repository in. Defaults to 57 | * {@code build/ghpages}. 58 | */ 59 | Object workingPath = "${project.buildDir}/ghpages" 60 | 61 | /** 62 | * Whether to delete existing files in the branch, replacing the 63 | * entire contents. Defaults to {@code true}. 64 | */ 65 | boolean deleteExistingFiles = true 66 | 67 | /** 68 | * The message used when committing changes to Github pages branch. 69 | * Defaults to 'Publish of Github pages from Gradle.'. 70 | */ 71 | String commitMessage = 'Publish of Github pages from Gradle.' 72 | 73 | /** 74 | * Constructs the plugin extension. 75 | * @param project the project to create 76 | * the extension for 77 | */ 78 | GithubPagesPluginExtension(Project project) { 79 | this.project = project 80 | this.pages = new DestinationCopySpec(project) 81 | pages.from 'src/main/ghpages' 82 | 83 | // defaulting the repoUri to the project repo's origin 84 | try { 85 | Grgit grgit = Grgit.open(currentDir: project.projectDir) 86 | this.repoUri = grgit.remote.list().find { it.name == 'origin' }?.url 87 | grgit.close() 88 | } catch (IllegalArgumentException e) { 89 | // there isn't a git repo 90 | this.repoUri = null 91 | } catch (GrgitException e) { 92 | // failed to open the repo of the current project 93 | this.repoUri = null 94 | } 95 | } 96 | 97 | /** 98 | * Gets the URI of the Github repository. This 99 | * will be used to clone the repository. 100 | * @return the repo URI 101 | */ 102 | String getRepoUri() { 103 | return ObjectUtil.unpackString(repoUri) 104 | } 105 | 106 | /** 107 | * Gets the working directory that the repo will be places in. 108 | * @return the working directory 109 | */ 110 | File getWorkingDir() { 111 | return project.file(workingPath) 112 | } 113 | 114 | /** 115 | * Configures the gh-pages copy spec. 116 | * @param closure the configuration closure 117 | */ 118 | void pages(Closure closure) { 119 | ConfigureUtil.configure(closure, pages) 120 | } 121 | 122 | /** 123 | * Configured the credentials to be used when interacting with 124 | * the repo. This will be passed a {@link PasswordCredentials} 125 | * instance. 126 | * @param closure the configuration closure 127 | */ 128 | void credentials(Closure closure) { 129 | ConfigureUtil.configure(closure, credentials) 130 | } 131 | 132 | static class DestinationCopySpec implements CopySpec { 133 | private final Project project 134 | private Object destPath 135 | 136 | @Delegate 137 | CopySpec realSpec 138 | 139 | DestinationCopySpec(Project project) { 140 | this.project = project 141 | this.realSpec = project.copySpec {} 142 | } 143 | 144 | String getRelativeDestinationDir() { 145 | return destPath ?: '.' 146 | } 147 | 148 | @Override 149 | CopySpec into(Object destPath) { 150 | this.destPath = destPath 151 | realSpec.into(destPath) 152 | return this 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/base/BaseReleasePlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.base 17 | 18 | import org.gradle.api.GradleException 19 | import org.gradle.api.Plugin 20 | import org.gradle.api.Project 21 | 22 | import org.slf4j.Logger 23 | import org.slf4j.LoggerFactory 24 | 25 | /** 26 | * Plugin providing the base structure of gradle-git's flavor of release 27 | * behavior. The plugin can be applied using the {@code org.ajoberstar.release-base} id. 28 | * 29 | *

30 | * The plugin adds the {@link ReleasePluginExtension} and a {@code release} task. 31 | *

32 | * 33 | * @see org.ajoberstar.gradle.git.release.opinion.Strategies 34 | * @see org.ajoberstar.gradle.git.release.opinion.OpinionReleasePlugin 35 | * @see Wiki Doc 36 | */ 37 | class BaseReleasePlugin implements Plugin { 38 | private static final Logger logger = LoggerFactory.getLogger(BaseReleasePlugin) 39 | private static final String PREPARE_TASK_NAME = 'prepare' 40 | private static final String RELEASE_TASK_NAME = 'release' 41 | 42 | void apply(Project project) { 43 | def extension = project.extensions.create('release', ReleasePluginExtension, project) 44 | addPrepareTask(project, extension) 45 | addReleaseTask(project, extension) 46 | project.plugins.withId('org.ajoberstar.grgit') { 47 | extension.grgit = project.grgit 48 | } 49 | } 50 | 51 | private void addPrepareTask(Project project, ReleasePluginExtension extension) { 52 | project.tasks.create(PREPARE_TASK_NAME) { 53 | description = 'Verifies that the project could be released.' 54 | doLast { 55 | ext.grgit = extension.grgit 56 | 57 | logger.info('Fetching changes from remote: {}', extension.remote) 58 | grgit.fetch(remote: extension.remote) 59 | 60 | // if branch is tracking another, make sure it's not behind 61 | if (grgit.branch.current.trackingBranch && grgit.branch.status(branch: grgit.branch.current.fullName).behindCount > 0) { 62 | throw new GradleException('Current branch is behind the tracked branch. Cannot release.') 63 | } 64 | } 65 | } 66 | 67 | project.tasks.all { task -> 68 | if (name != PREPARE_TASK_NAME) { 69 | task.shouldRunAfter PREPARE_TASK_NAME 70 | } 71 | } 72 | } 73 | 74 | private void addReleaseTask(Project project, ReleasePluginExtension extension) { 75 | project.tasks.create(RELEASE_TASK_NAME) { 76 | description = 'Releases this project.' 77 | dependsOn PREPARE_TASK_NAME 78 | doLast { 79 | // force version inference if it hasn't happened already 80 | project.version.toString() 81 | 82 | ext.grgit = extension.grgit 83 | ext.toPush = [] 84 | 85 | // if not on detached HEAD, push branch 86 | if (grgit.branch.current.fullName != 'HEAD') { 87 | ext.toPush << grgit.branch.current.fullName 88 | } 89 | 90 | ext.tagName = extension.tagStrategy.maybeCreateTag(grgit, project.version.inferredVersion) 91 | if (tagName) { 92 | toPush << tagName 93 | } 94 | 95 | if (toPush) { 96 | logger.warn('Pushing changes in {} to {}', toPush, extension.remote) 97 | grgit.push(remote: extension.remote, refsOrSpecs: toPush) 98 | } else { 99 | logger.warn('Nothing to push.') 100 | } 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/base/DefaultVersionStrategy.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.base 17 | 18 | import org.ajoberstar.grgit.Grgit 19 | 20 | import org.gradle.api.Project 21 | 22 | /** 23 | * Strategy to infer a version from the project's and Git repository's state. This 24 | * also supports being selected as a default strategy. This is a temporary interface 25 | * and should be replaced in some other way in gradle-git 2.0.0. 26 | * @see org.ajoberstar.gradle.git.release.semver.SemVerStrategy 27 | * @see org.ajoberstar.gradle.git.release.opinion.Strategies 28 | */ 29 | interface DefaultVersionStrategy extends VersionStrategy { 30 | /** 31 | * Determines if the strategy can be used as a default strategy for inferring 32 | * the project's version. A return of {@code false} does not mean that the 33 | * strategy cannot be used as the default. 34 | * @param project the project the version should be inferred for 35 | * @param grgit the repository the version should be inferred from 36 | * @return {@code true} if the strategy can be used to infer the version 37 | */ 38 | boolean defaultSelector(Project project, Grgit grgit) 39 | } 40 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/base/ReleasePluginExtension.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.base 17 | 18 | import org.ajoberstar.grgit.Grgit 19 | import org.ajoberstar.grgit.util.ConfigureUtil 20 | 21 | import org.gradle.api.GradleException 22 | import org.gradle.api.Project 23 | 24 | import org.slf4j.Logger 25 | import org.slf4j.LoggerFactory 26 | 27 | /** 28 | * Extension providing configuration options for gradle-git's release plugins. 29 | * 30 | *

31 | * Sets the version to a {@link DelayedVersion} which will infer the version 32 | * when {@code toString()} is called on it. A strategy will be selected from the 33 | * ones configured on this extension and then used to infer the version. 34 | *

35 | * 36 | * @see org.ajoberstar.gradle.git.release.base.BaseReleasePlugin 37 | * @see org.ajoberstar.gradle.git.release.opinion.OpinionReleasePlugin 38 | */ 39 | class ReleasePluginExtension { 40 | private static final Logger logger = LoggerFactory.getLogger(ReleasePluginExtension) 41 | protected final Project project 42 | private final Map versionStrategies = [:] 43 | 44 | /** 45 | * The strategy to use when creating a tag for the inferred version. 46 | */ 47 | final TagStrategy tagStrategy = new TagStrategy() 48 | 49 | /** 50 | * The strategy to use if all of the ones in {@code versionStrategies} return 51 | * false from their {@code selector()} methods. This strategy can be, but is 52 | * not required to be, one from {@code versionStrategies}. 53 | */ 54 | VersionStrategy defaultVersionStrategy 55 | 56 | /** 57 | * The repository to infer the version from. 58 | */ 59 | Grgit grgit 60 | 61 | /** 62 | * The remote to fetch changes from and push changes to. 63 | */ 64 | String remote = 'origin' 65 | 66 | ReleasePluginExtension(Project project) { 67 | this.project = project 68 | def sharedVersion = new DelayedVersion() 69 | project.rootProject.allprojects { 70 | version = sharedVersion 71 | } 72 | } 73 | 74 | /** 75 | * Gets all strategies in the order they were inserted into the extension. 76 | */ 77 | List getVersionStrategies() { 78 | return versionStrategies.collect { key, value -> value }.asImmutable() 79 | } 80 | 81 | /** 82 | * Adds a strategy to the extension. If the strategy has the same name as 83 | * one already configured, it will replace the existing one. 84 | */ 85 | void versionStrategy(VersionStrategy strategy) { 86 | versionStrategies[strategy.name] = strategy 87 | } 88 | 89 | /** 90 | * Configures the tag strategy with the provided closure. 91 | */ 92 | void tagStrategy(Closure closure) { 93 | ConfigureUtil.configure(tagStrategy, closure) 94 | } 95 | 96 | // TODO: Decide if this should be thread-safe. 97 | private class DelayedVersion { 98 | ReleaseVersion inferredVersion 99 | 100 | private void infer() { 101 | VersionStrategy selectedStrategy = versionStrategies.find { strategy -> 102 | strategy.selector(project, grgit) 103 | } 104 | 105 | if (!selectedStrategy) { 106 | boolean useDefault 107 | if (defaultVersionStrategy instanceof DefaultVersionStrategy) { 108 | useDefault = defaultVersionStrategy.defaultSelector(project, grgit) 109 | } else { 110 | useDefault = defaultVersionStrategy?.selector(project, grgit) 111 | } 112 | 113 | if (useDefault) { 114 | logger.info('Falling back to default strategy: {}', defaultVersionStrategy.name) 115 | selectedStrategy = defaultVersionStrategy 116 | } else { 117 | throw new GradleException('No version strategies were selected. Run build with --info for more detail.') 118 | } 119 | } 120 | 121 | inferredVersion = selectedStrategy.infer(project, grgit) 122 | } 123 | 124 | @Override 125 | String toString() { 126 | if (!inferredVersion) { 127 | infer() 128 | } 129 | return inferredVersion.version 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/base/ReleaseVersion.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.base 17 | 18 | import groovy.transform.Immutable 19 | 20 | /** 21 | * Represents an inferred version and any related metadata to be used after the 22 | * inference. 23 | */ 24 | @Immutable 25 | class ReleaseVersion { 26 | /** 27 | * The version that should be used by the project. 28 | */ 29 | String version 30 | /** 31 | * The latest version, as determined by the strategy's logic. 32 | */ 33 | String previousVersion 34 | /** 35 | * Whether or not to create a tag for the release. 36 | */ 37 | boolean createTag 38 | } 39 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/base/TagStrategy.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.base 17 | 18 | import com.github.zafarkhaja.semver.ParseException 19 | import com.github.zafarkhaja.semver.Version 20 | import org.ajoberstar.grgit.Grgit 21 | import org.ajoberstar.grgit.Tag 22 | import org.slf4j.Logger 23 | import org.slf4j.LoggerFactory 24 | 25 | /** 26 | * Strategy for creating a Git tag associated with a release. 27 | */ 28 | class TagStrategy { 29 | 30 | /** 31 | * Closure taking a version String as an argument and returning a string to be used as a tag name. 32 | */ 33 | Closure toTagString 34 | 35 | /** 36 | * Closure taking a {@link Tag tag} as an argument and returning a {@link Version version} if the tag could be 37 | * parsed, else 'null' 38 | */ 39 | Closure parseTag = { Tag tag -> 40 | try { 41 | Version.valueOf(tag.name[0] == 'v' ? tag.name[1..-1] : tag.name) 42 | } catch (ParseException e) { 43 | null 44 | } 45 | } 46 | 47 | TagStrategy() { 48 | setPrefixNameWithV(true) 49 | } 50 | 51 | private static final Logger logger = LoggerFactory.getLogger(TagStrategy) 52 | 53 | /** 54 | * Added for backwards compatibility. 55 | * @param prefix whether or not to prefix the tag with a 'v' 56 | */ 57 | void setPrefixNameWithV(boolean prefix) { 58 | toTagString = { versionString -> prefix ? "v${versionString}" : versionString } 59 | } 60 | 61 | /** 62 | * Closure taking a {@link ReleaseVersion} as an argument and returning 63 | * a string to be used as the tag's message. 64 | */ 65 | Closure generateMessage = { version -> "Release of ${version.version}" } 66 | 67 | /** 68 | * If the release version specifies a tag should be created, create a tag 69 | * using the provided {@code Grgit} instance and this instance's state to 70 | * determine the tag name and message. 71 | * @param grgit the repository to create the tag in 72 | * @param version the version to create the tag for 73 | * @return the name of the tag created, or {@code null} if it wasn't 74 | */ 75 | String maybeCreateTag(Grgit grgit, ReleaseVersion version) { 76 | if (version.createTag) { 77 | String name = toTagString(version.version) 78 | String message = generateMessage(version) 79 | 80 | logger.warn('Tagging repository as {}', name) 81 | grgit.tag.add(name: name, message: message) 82 | return name 83 | } else { 84 | return null 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/base/VersionStrategy.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.base 17 | 18 | import org.ajoberstar.grgit.Grgit 19 | 20 | import org.gradle.api.Project 21 | 22 | /** 23 | * Strategy to infer a version from the project's and Git repository's state. 24 | * @see org.ajoberstar.gradle.git.release.semver.SemVerStrategy 25 | * @see org.ajoberstar.gradle.git.release.opinion.Strategies 26 | */ 27 | interface VersionStrategy { 28 | /** 29 | * The name of the strategy. 30 | * @return the name of the strategy 31 | */ 32 | String getName() 33 | 34 | /** 35 | * Determines if the strategy should be used to infer the project's version. 36 | * A return of {@code false} does not mean that the strategy cannot be used 37 | * as the default. 38 | * @param project the project the version should be inferred for 39 | * @param grgit the repository the version should be inferred from 40 | * @return {@code true} if the strategy should be used to infer the version 41 | */ 42 | boolean selector(Project project, Grgit grgit) 43 | 44 | /** 45 | * Infers the project version from the repository. 46 | * @param project the project the version should be inferred for 47 | * @param grgit the repository the version should be inferred from 48 | * @return the inferred version 49 | */ 50 | ReleaseVersion infer(Project project, Grgit grgit) 51 | } 52 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/base/package-info.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * The core structure of gradle-git's flavor of release behavior. The code 18 | * provided in this package can be used on its own, as long as users provide 19 | * their own implementation of {@code VersionStrategy}. 20 | */ 21 | package org.ajoberstar.gradle.git.release.base 22 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/experimental/ExperimentalReleasePlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.experimental 17 | 18 | import org.gradle.api.GradleException 19 | import org.gradle.api.Plugin 20 | import org.gradle.api.Project 21 | 22 | import org.slf4j.Logger 23 | import org.slf4j.LoggerFactory 24 | 25 | /** 26 | * Experimental release plugin that removes some previous coupling to extensions. 27 | * Inteded to support semver-vcs, but may serve as a better minimal base. 28 | * @since 1.3.0 29 | */ 30 | class ExperimentalReleasePlugin implements Plugin { 31 | private static final Logger logger = LoggerFactory.getLogger(ExperimentalReleasePlugin) 32 | private static final String PREPARE_TASK_NAME = 'prepare' 33 | private static final String RELEASE_TASK_NAME = 'release' 34 | 35 | void apply(Project project) { 36 | project.plugins.apply('org.ajoberstar.grgit') 37 | addPrepareTask(project) 38 | addReleaseTask(project) 39 | } 40 | 41 | private void addPrepareTask(Project project) { 42 | project.tasks.create(PREPARE_TASK_NAME) { 43 | description = 'Verifies that the project could be released.' 44 | doLast { 45 | logger.info('Fetching changes from remote.') 46 | project.grgit.fetch() 47 | 48 | if (project.grgit.branch.status(branch: project.grgit.branch.current).behindCount > 0) { 49 | throw new GradleException('Current branch is behind the tracked branch. Cannot release.') 50 | } 51 | } 52 | } 53 | 54 | project.tasks.all { task -> 55 | if (name != PREPARE_TASK_NAME) { 56 | task.shouldRunAfter PREPARE_TASK_NAME 57 | } 58 | } 59 | } 60 | 61 | private void addReleaseTask(Project project) { 62 | project.tasks.create(RELEASE_TASK_NAME) { 63 | description = 'Releases this project.' 64 | dependsOn PREPARE_TASK_NAME 65 | doLast { 66 | ext.toPush = [project.grgit.branch.current.fullName] 67 | 68 | // force version inference if it hasn't happened already 69 | ext.tagName = project.version.toString() 70 | if (tagName) { 71 | toPush << tagName 72 | } 73 | 74 | logger.warn('Pushing changes in {} to remote.', toPush) 75 | project.grgit.push(refsOrSpecs: toPush) 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/opinion/OpinionReleasePlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.opinion 17 | 18 | import org.ajoberstar.gradle.git.release.semver.RebuildVersionStrategy 19 | import org.ajoberstar.grgit.Grgit 20 | import org.ajoberstar.grgit.exception.GrgitException 21 | 22 | import org.gradle.api.Plugin 23 | import org.gradle.api.Project 24 | 25 | /** 26 | * Plugin providing the base structure of gradle-git's flavor of release 27 | * behavior. The plugin can be applied using the {@code org.ajoberstar.release-base} id. 28 | * 29 | *

30 | * The plugin applies the {@code org.ajoberstar.release-base} plugin and configures it to 31 | * use the following strategies (in order): 32 | *

33 | * 34 | *
    35 | *
  • {@link RebuildVersionStrategy}
  • 36 | *
  • {@link Strategies#DEVELOPMENT} (also set as the default)
  • 37 | *
  • {@link Strategies#PRE_RELEASE}
  • 38 | *
  • {@link Strategies#FINAL}
  • 39 | *
40 | * 41 | *

42 | * Additionally it configures the tag strategy to generate a message from 43 | * the short messages of the commits since the previous version. 44 | *

45 | * 46 | * @see org.ajoberstar.gradle.git.release.opinion.Strategies 47 | * @see org.ajoberstar.gradle.git.release.base.BaseReleasePlugin 48 | * @see Wiki Doc 49 | */ 50 | class OpinionReleasePlugin implements Plugin { 51 | void apply(Project project) { 52 | project.plugins.apply('org.ajoberstar.release-base') 53 | 54 | project.release { 55 | versionStrategy RebuildVersionStrategy.INSTANCE 56 | versionStrategy Strategies.DEVELOPMENT 57 | versionStrategy Strategies.PRE_RELEASE 58 | versionStrategy Strategies.FINAL 59 | defaultVersionStrategy = Strategies.DEVELOPMENT 60 | tagStrategy { 61 | generateMessage = { version -> 62 | StringBuilder builder = new StringBuilder() 63 | builder << 'Release of ' 64 | builder << version.version 65 | builder << '\n\n' 66 | 67 | String previousVersion = "${project.release.tagStrategy.toTagString(version.previousVersion)}^{commit}" 68 | List excludes = [] 69 | if (tagExists(grgit, previousVersion)) { 70 | excludes << previousVersion 71 | } 72 | grgit.log( 73 | includes: ['HEAD'], 74 | excludes: excludes 75 | ).inject(builder) { bldr, commit -> 76 | bldr << '- ' 77 | bldr << commit.shortMessage 78 | bldr << '\n' 79 | } 80 | builder.toString() 81 | } 82 | } 83 | } 84 | } 85 | 86 | private boolean tagExists(Grgit grgit, String revStr) { 87 | try { 88 | grgit.resolve.toCommit(revStr) 89 | return true 90 | } catch (GrgitException e) { 91 | return false 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/opinion/Strategies.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.opinion 17 | 18 | import static org.ajoberstar.gradle.git.release.semver.StrategyUtil.* 19 | 20 | import java.util.regex.Pattern 21 | 22 | import org.ajoberstar.gradle.git.release.semver.ChangeScope 23 | import org.ajoberstar.gradle.git.release.semver.PartialSemVerStrategy 24 | import org.ajoberstar.gradle.git.release.semver.SemVerStrategy 25 | 26 | import org.gradle.api.GradleException 27 | 28 | /** 29 | * Opinionated sample strategies. These can either be used as-is or as an 30 | * example for others. 31 | * @see org.ajoberstar.gradle.git.release.base.VersionStrategy 32 | * @see org.ajoberstar.gradle.git.release.semver.SemVerStrategy 33 | * @see org.ajoberstar.gradle.git.release.semver.SemVerStrategyState 34 | * @see org.ajoberstar.gradle.git.release.semver.PartialSemVerStrategy 35 | */ 36 | final class Strategies { 37 | /** 38 | * Sample strategies that infer the normal component of a version. 39 | */ 40 | static final class Normal { 41 | /** 42 | * Increments the nearest normal version using the scope specified 43 | * in the {@link SemVerStrategyState#scopeFromProp}. 44 | */ 45 | static final PartialSemVerStrategy USE_SCOPE_PROP = closure { state -> 46 | return incrementNormalFromScope(state, state.scopeFromProp) 47 | } 48 | 49 | /** 50 | * If the nearest any is different from the nearest normal, sets the 51 | * normal component to the nearest any's normal component. Otherwise 52 | * do nothing. 53 | * 54 | *

55 | * For example, if the nearest any is {@code 1.2.3-alpha.1} and the 56 | * nearest normal is {@code 1.2.2}, this will infer the normal 57 | * component as {@code 1.2.3}. 58 | *

59 | */ 60 | static final PartialSemVerStrategy USE_NEAREST_ANY = closure { state -> 61 | def nearest = state.nearestVersion 62 | if (nearest.any == nearest.normal) { 63 | return state 64 | } else { 65 | return state.copyWith(inferredNormal: nearest.any.normalVersion) 66 | } 67 | } 68 | 69 | /** 70 | * Enforces that the normal version complies with the current branch's major version. 71 | * If the branch is not in the format {@code #.x} (e.g. {@code 2.x}), this will do 72 | * nothing. 73 | * 74 | *
    75 | *
  • If the current branch doesn't match the pattern do nothing.
  • 76 | *
  • If the the nearest normal already complies with the branch name.
  • 77 | *
  • If the major component can be incremented to comply with the branch, do so.
  • 78 | *
  • Otherwise fail, because the version can't comply with the branch.
  • 79 | *
80 | */ 81 | static final PartialSemVerStrategy ENFORCE_BRANCH_MAJOR_X = fromBranchPattern(~/^(\d+)\.x$/) 82 | 83 | /** 84 | * Enforces that the normal version complies with the current branch's major version. 85 | * If the branch is not in the format {@code release/#.x} (e.g. {@code release/2.x}) or 86 | * {@code release-#.x} (e.g. {@code release-3.x}, this will do nothing. 87 | * 88 | *
    89 | *
  • If the current branch doesn't match the pattern do nothing.
  • 90 | *
  • If the the nearest normal already complies with the branch name.
  • 91 | *
  • If the major component can be incremented to comply with the branch, do so.
  • 92 | *
  • Otherwise fail, because the version can't comply with the branch.
  • 93 | *
94 | */ 95 | static final PartialSemVerStrategy ENFORCE_GITFLOW_BRANCH_MAJOR_X = fromBranchPattern(~/^release(?:\/|-)(\d+)\.x$/) 96 | 97 | /** 98 | * Enforces that the normal version complies with the current branch's major version. 99 | * If the branch is not in the format {@code #.#.x} (e.g. {@code 2.3.x}), this will do 100 | * nothing. 101 | * 102 | *
    103 | *
  • If the current branch doesn't match the pattern do nothing.
  • 104 | *
  • If the the nearest normal already complies with the branch name.
  • 105 | *
  • If the major component can be incremented to comply with the branch, do so.
  • 106 | *
  • If the minor component can be incremented to comply with the branch, do so.
  • 107 | *
  • Otherwise fail, because the version can't comply with the branch.
  • 108 | *
109 | */ 110 | static final PartialSemVerStrategy ENFORCE_BRANCH_MAJOR_MINOR_X = fromBranchPattern(~/^(\d+)\.(\d+)\.x$/) 111 | 112 | /** 113 | * Enforces that the normal version complies with the current branch's major version. 114 | * If the branch is not in the format {@code release/#.#.x} (e.g. {@code release/2.3.x}) or 115 | * {@code release-#.#.x} (e.g. {@code release-3.11.x}, this will do nothing. 116 | * 117 | *
    118 | *
  • If the current branch doesn't match the pattern do nothing.
  • 119 | *
  • If the the nearest normal already complies with the branch name.
  • 120 | *
  • If the major component can be incremented to comply with the branch, do so.
  • 121 | *
  • Otherwise fail, because the version can't comply with the branch.
  • 122 | *
123 | */ 124 | static final PartialSemVerStrategy ENFORCE_GITFLOW_BRANCH_MAJOR_MINOR_X = fromBranchPattern(~/^release(?:\/|-)(\d+)\.(\d+)\.x$/) 125 | 126 | /** 127 | * Uses the specified pattern to enforce that versions inferred on this branch 128 | * comply. Patterns should have 1 or 2 capturing groups representing the 129 | * major and, optionally, the minor component of the version. 130 | * 131 | *
    132 | *
  • If the current branch doesn't match the pattern do nothing.
  • 133 | *
  • If only the major is specified in the branch name, and the nearest normal complies with that major, do nothing.
  • 134 | *
  • If the patch component can be incremented and still comply with the branch, do so.
  • 135 | *
  • If the minor component can be incremented to comply with the branch, do so.
  • 136 | *
  • If the major component can be incremented to comply with the branch, do so.
  • 137 | *
  • Otherwise fail, because the version can't comply with the branch.
  • 138 | *
139 | */ 140 | static PartialSemVerStrategy fromBranchPattern(Pattern pattern) { 141 | return closure { state -> 142 | def m = state.currentBranch.name =~ pattern 143 | if (m) { 144 | def major = m.groupCount() >= 1 ? parseIntOrZero(m[0][1]) : -1 145 | def minor = m.groupCount() >= 2 ? parseIntOrZero(m[0][2]) : -1 146 | 147 | def normal = state.nearestVersion.normal 148 | def majorDiff = major - normal.majorVersion 149 | def minorDiff = minor - normal.minorVersion 150 | 151 | if (majorDiff == 1 && minor <= 0) { 152 | // major is off by one and minor is either 0 or not in the branch name 153 | return incrementNormalFromScope(state, ChangeScope.MAJOR) 154 | } else if (minorDiff == 1 && minor > 0) { 155 | // minor is off by one and specified in the branch name 156 | return incrementNormalFromScope(state, ChangeScope.MINOR) 157 | } else if (majorDiff == 0 && minorDiff == 0 && minor >= 0) { 158 | // major and minor match, both are specified in branch name 159 | return incrementNormalFromScope(state, ChangeScope.PATCH) 160 | } else if (majorDiff == 0 && minor < 0) { 161 | // only major specified in branch name and already matches 162 | return state 163 | } else { 164 | throw new GradleException("Invalid branch (${state.currentBranch.name}) for nearest normal (${normal}).") 165 | } 166 | } else { 167 | return state 168 | } 169 | } 170 | } 171 | 172 | /** 173 | * Always use the scope provided to increment the normal component. 174 | */ 175 | static PartialSemVerStrategy useScope(ChangeScope scope) { 176 | return closure { state -> incrementNormalFromScope(state, scope) } 177 | } 178 | } 179 | 180 | /** 181 | * Sample strategies that infer the pre-release component of a version. 182 | */ 183 | static final class PreRelease { 184 | /** 185 | * Do not modify the pre-release component. 186 | */ 187 | static final PartialSemVerStrategy NONE = closure { state -> state } 188 | 189 | /** 190 | * Sets the pre-release component to the value of {@link SemVerStrategyState#stageFromProp}. 191 | */ 192 | static final PartialSemVerStrategy STAGE_FIXED = closure { state -> state.copyWith(inferredPreRelease: state.stageFromProp)} 193 | 194 | /** 195 | * If the value of {@link SemVerStrategyState#stageFromProp} has a higher or the same precedence than 196 | * the nearest any's pre-release component, set the pre-release component to 197 | * {@link SemVerStrategyState#scopeFromProp}. If not, append the {@link SemVerStrategyState#scopeFromProp} 198 | * to the nearest any's pre-release. 199 | */ 200 | static final PartialSemVerStrategy STAGE_FLOAT = closure { state -> 201 | def sameNormal = state.inferredNormal == state.nearestVersion.any.normalVersion 202 | def nearestAnyPreRelease = state.nearestVersion.any.preReleaseVersion 203 | if (sameNormal && nearestAnyPreRelease != null && nearestAnyPreRelease > state.stageFromProp) { 204 | state.copyWith(inferredPreRelease: "${nearestAnyPreRelease}.${state.stageFromProp}") 205 | } else { 206 | state.copyWith(inferredPreRelease: state.stageFromProp) 207 | } 208 | } 209 | 210 | /** 211 | * If the nearest any's pre-release component starts with the so far inferred pre-release component, 212 | * increment the count of the nearest any and append it to the so far inferred pre-release 213 | * component. Otherwise append 1 to the so far inferred pre-release component. 214 | */ 215 | static final PartialSemVerStrategy COUNT_INCREMENTED = closure { state -> 216 | def nearest = state.nearestVersion 217 | def currentPreIdents = state.inferredPreRelease ? state.inferredPreRelease.split('\\.') as List : [] 218 | if (nearest.any == nearest.normal || nearest.any.normalVersion != state.inferredNormal) { 219 | currentPreIdents << '1' 220 | } else { 221 | def nearestPreIdents = nearest.any.preReleaseVersion.split('\\.') 222 | if (nearestPreIdents.size() <= currentPreIdents.size()) { 223 | currentPreIdents << '1' 224 | } else if (currentPreIdents == nearestPreIdents[0..(currentPreIdents.size() - 1)]) { 225 | def count = parseIntOrZero(nearestPreIdents[currentPreIdents.size()]) 226 | currentPreIdents << Integer.toString(count + 1) 227 | } else { 228 | currentPreIdents << '1' 229 | } 230 | } 231 | return state.copyWith(inferredPreRelease: currentPreIdents.join('.')) 232 | } 233 | 234 | /** 235 | * Append the count of commits since the nearest any to the so far inferred pre-release component. 236 | */ 237 | static final PartialSemVerStrategy COUNT_COMMITS_SINCE_ANY = closure { state -> 238 | def count = state.nearestVersion.distanceFromAny 239 | def inferred = state.inferredPreRelease ? "${state.inferredPreRelease}.${count}" : "${count}" 240 | return state.copyWith(inferredPreRelease: inferred) 241 | } 242 | 243 | /** 244 | * If the repo has uncommitted changes append "uncommitted" to the so far inferred pre-release component. 245 | */ 246 | static final PartialSemVerStrategy SHOW_UNCOMMITTED = closure { state -> 247 | if (state.repoDirty) { 248 | def inferred = state.inferredPreRelease ? "${state.inferredPreRelease}.uncommitted" : 'uncommitted' 249 | state.copyWith(inferredPreRelease: inferred) 250 | } else { 251 | state 252 | } 253 | } 254 | } 255 | 256 | /** 257 | * Sample strategies that infer the build metadata component of a version. 258 | */ 259 | static final class BuildMetadata { 260 | /** 261 | * Do not modify the build metadata. 262 | */ 263 | static final PartialSemVerStrategy NONE = closure { state -> state } 264 | /** 265 | * Set the build metadata to the abbreviated ID of the current HEAD. 266 | */ 267 | static final PartialSemVerStrategy COMMIT_ABBREVIATED_ID = closure { state -> state.copyWith(inferredBuildMetadata: state.currentHead.abbreviatedId) } 268 | /** 269 | * Set the build metadata to the full ID of the current HEAD. 270 | */ 271 | static final PartialSemVerStrategy COMMIT_FULL_ID = closure { state -> state.copyWith(inferredBuildMetadata: state.currentHead.id) } 272 | /** 273 | * Set the build metadata to the current timestamp in {@code YYYY.MM.DD.HH.MM.SS} format. 274 | */ 275 | static final PartialSemVerStrategy TIMESTAMP = closure { state -> state.copyWith(inferredBuildMetadata: new Date().format('yyyy.MM.dd.hh.mm.ss')) } 276 | } 277 | 278 | /** 279 | * Provides opinionated defaults for a strategy. The primary behavior is for the normal component. 280 | * If the {@code release.scope} property is set, use it. Or if the nearest any's normal component is different 281 | * than the nearest normal version, use it. Or, if nothing else, use PATCH scope. 282 | */ 283 | static final SemVerStrategy DEFAULT = new SemVerStrategy( 284 | name: '', 285 | stages: [] as SortedSet, 286 | allowDirtyRepo: false, 287 | normalStrategy: one(Normal.USE_SCOPE_PROP, Normal.USE_NEAREST_ANY, Normal.useScope(ChangeScope.PATCH)), 288 | preReleaseStrategy: PreRelease.NONE, 289 | buildMetadataStrategy: BuildMetadata.NONE, 290 | createTag: true, 291 | enforcePrecedence: true 292 | ) 293 | 294 | /** 295 | * Provides a single "SNAPSHOT" stage that can be used in dirty repos and will 296 | * not enforce precedence. The pre-release compoment will always be "SNAPSHOT" 297 | * and no build metadata will be used. Tags will not be created for these versions. 298 | */ 299 | static final SemVerStrategy SNAPSHOT = DEFAULT.copyWith( 300 | name: 'snapshot', 301 | stages: ['SNAPSHOT'] as SortedSet, 302 | allowDirtyRepo: true, 303 | preReleaseStrategy: PreRelease.STAGE_FIXED, 304 | createTag: false, 305 | enforcePrecedence: false 306 | ) 307 | 308 | /** 309 | * Provides a single "dev" stage that can be used in dirty repos but will 310 | * enforce precedence. If this strategy is used after a nearest any with a 311 | * higher precedence pre-release component (e.g. "rc.1"), the dev component 312 | * will be appended rather than replace. The commit count since the nearest 313 | * any will be used to disambiguate versions and the pre-release component 314 | * will note if the repository is dirty. The abbreviated ID of the HEAD will 315 | * be used as build metadata. 316 | */ 317 | static final SemVerStrategy DEVELOPMENT = DEFAULT.copyWith( 318 | name: 'development', 319 | stages: ['dev'] as SortedSet, 320 | allowDirtyRepo: true, 321 | preReleaseStrategy: all(PreRelease.STAGE_FLOAT, PreRelease.COUNT_COMMITS_SINCE_ANY, PreRelease.SHOW_UNCOMMITTED), 322 | buildMetadataStrategy: BuildMetadata.COMMIT_ABBREVIATED_ID, 323 | createTag: false 324 | ) 325 | 326 | /** 327 | * Provides "milestone" and "rc" stages that can only be used in clean repos 328 | * and will enforce precedence. The pre-release component will always be set 329 | * to the stage with an incremented count to disambiguate successive 330 | * releases of the same stage. No build metadata component will be added. Please 331 | * note that this strategy uses the same name as {@code PRE_RELEASE_ALPHA_BETA} 332 | * so it cannot be used at the same time. 333 | */ 334 | static final SemVerStrategy PRE_RELEASE = DEFAULT.copyWith( 335 | name: 'pre-release', 336 | stages: ['milestone', 'rc'] as SortedSet, 337 | preReleaseStrategy: all(PreRelease.STAGE_FIXED, PreRelease.COUNT_INCREMENTED) 338 | ) 339 | 340 | /** 341 | * Provides "alpha", "beta" and "rc" stages that can only be used in clean repos 342 | * and will enforce precedence. The pre-release-alpha-beta component will always be set 343 | * to the stage with an incremented count to disambiguate successive 344 | * releases of the same stage. No build metadata component will be added. Please note 345 | * that this strategy uses the same name as {@code PRE_RELEASE} so it cannot be used 346 | * at the same time. 347 | */ 348 | static final SemVerStrategy PRE_RELEASE_ALPHA_BETA = PRE_RELEASE.copyWith( 349 | name: 'pre-release', 350 | stages: ['alpha', 'beta', 'rc'] as SortedSet 351 | ) 352 | 353 | /** 354 | * Provides a single "final" stage that can only be used in clean repos and 355 | * will enforce precedence. The pre-release and build metadata components 356 | * will always be empty. 357 | */ 358 | static final SemVerStrategy FINAL = DEFAULT.copyWith( 359 | name: 'final', 360 | stages: ['final'] as SortedSet 361 | ) 362 | } 363 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/opinion/package-info.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Opinionated plugin and set of strategies build on top of the base package. 18 | */ 19 | package org.ajoberstar.gradle.git.release.opinion 20 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/semver/ChangeScope.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.semver 17 | 18 | enum ChangeScope { 19 | MAJOR, 20 | MINOR, 21 | PATCH 22 | } 23 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/semver/NearestVersion.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.semver 17 | 18 | import groovy.transform.Immutable 19 | 20 | import com.github.zafarkhaja.semver.Version 21 | 22 | /** 23 | * Nearest version tags reachable from the current HEAD. The version 0.0.0 24 | * will be returned for any 25 | * @since 0.8.0 26 | */ 27 | @Immutable(knownImmutableClasses=[Version]) 28 | class NearestVersion { 29 | /** 30 | * The nearest version that is tagged. 31 | */ 32 | Version any 33 | 34 | /** 35 | * The nearest normal (i.e. non-prerelease) version that is tagged. 36 | */ 37 | Version normal 38 | 39 | /** 40 | * The number of commits since {@code any} reachable from HEAD. 41 | */ 42 | int distanceFromAny 43 | 44 | /** 45 | * The number of commits since {@code normal} reachable from HEAD. 46 | */ 47 | int distanceFromNormal 48 | } 49 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/semver/NearestVersionLocator.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.semver 17 | 18 | import com.github.zafarkhaja.semver.Version 19 | import org.ajoberstar.gradle.git.release.base.TagStrategy 20 | import org.ajoberstar.grgit.Grgit 21 | import org.ajoberstar.grgit.Tag 22 | import org.eclipse.jgit.lib.ObjectId 23 | import org.eclipse.jgit.revwalk.RevCommit 24 | import org.eclipse.jgit.revwalk.RevWalk 25 | import org.eclipse.jgit.revwalk.RevWalkUtils 26 | import org.slf4j.Logger 27 | import org.slf4j.LoggerFactory 28 | 29 | /** 30 | * Locates the nearest {@link org.ajoberstar.grgit.Tag tag}s whose names can be 31 | * parsed as a {@link com.github.zafarkhaja.semver.Version version}. Both the 32 | * absolute nearest version tag and the nearest "normal version" tag are 33 | * included. 34 | * 35 | *

36 | * Primarily used as part of version inference to determine the previous 37 | * version. 38 | *

39 | * 40 | * @since 0.8.0 41 | */ 42 | class NearestVersionLocator { 43 | private static final Logger logger = LoggerFactory.getLogger(NearestVersionLocator) 44 | private static final Version UNKNOWN = Version.valueOf('0.0.0') 45 | 46 | final TagStrategy strategy 47 | 48 | NearestVersionLocator(TagStrategy strategy) { 49 | this.strategy = strategy 50 | } 51 | 52 | /** 53 | * Locate the nearest version in the given repository 54 | * starting from the current HEAD. 55 | * 56 | *

57 | * All tag names are parsed to determine if they are valid 58 | * version strings. Tag names can begin with "v" (which will 59 | * be stripped off). 60 | *

61 | * 62 | *

63 | * The nearest tag is determined by getting a commit log between 64 | * the tag and {@code HEAD}. The version tag with the smallest 65 | * log from a pure count of commits will have its version returned. If two 66 | * version tags have a log of the same size, the versions will be compared 67 | * to find the one with the highest precedence according to semver rules. 68 | * For example, {@code 1.0.0} has higher precedence than {@code 1.0.0-rc.2}. 69 | * For tags with logs of the same size and versions of the same precedence 70 | * it is undefined which will be returned. 71 | *

72 | * 73 | *

74 | * Two versions will be returned: the "any" version and the "normal" version. 75 | * "Any" is the absolute nearest tagged version. "Normal" is the nearest 76 | * tagged version that does not include a pre-release segment. 77 | *

78 | * 79 | * @param grgit the repository to locate the tag in 80 | * @param fromRevStr the revision to consider current. 81 | * Defaults to {@code HEAD}. 82 | * @return the version corresponding to the nearest tag 83 | */ 84 | NearestVersion locate(Grgit grgit) { 85 | logger.debug('Locate beginning on branch: {}', grgit.branch.current.fullName) 86 | 87 | // Reuse a single walk to make use of caching. 88 | RevWalk walk = new RevWalk(grgit.repository.jgit.repo) 89 | try { 90 | walk.retainBody = false 91 | 92 | def toRev = { obj -> 93 | def commit = grgit.resolve.toCommit(obj) 94 | def id = ObjectId.fromString(commit.id) 95 | walk.parseCommit(id) 96 | } 97 | 98 | List tags = grgit.tag.list().collect { tag -> 99 | [version: strategy.parseTag(tag), tag: tag, rev: toRev(tag)] 100 | }.findAll { 101 | it.version 102 | } 103 | 104 | List normalTags = tags.findAll { !it.version.preReleaseVersion } 105 | RevCommit head = toRev(grgit.head()) 106 | 107 | def normal = findNearestVersion(walk, head, normalTags) 108 | def any = findNearestVersion(walk, head, tags) 109 | 110 | logger.debug('Nearest release: {}, nearest any: {}.', normal, any) 111 | return new NearestVersion(any.version, normal.version, any.distance, normal.distance) 112 | } finally { 113 | walk.close() 114 | } 115 | } 116 | 117 | private Map findNearestVersion(RevWalk walk, RevCommit head, List versionTags) { 118 | walk.reset() 119 | walk.markStart(head) 120 | Map versionTagsByRev = versionTags.groupBy { it.rev } 121 | 122 | def reachableVersionTags = walk.collectMany { rev -> 123 | def matches = versionTagsByRev[rev] 124 | if (matches) { 125 | // Parents can't be "nearer". Exclude them to avoid extra walking. 126 | rev.parents.each { walk.markUninteresting(it) } 127 | } 128 | matches ?: [] 129 | }.each { versionTag -> 130 | versionTag.distance = RevWalkUtils.count(walk, head, versionTag.rev) 131 | } 132 | 133 | if (reachableVersionTags) { 134 | return reachableVersionTags.min { a, b -> 135 | def distanceCompare = a.distance <=> b.distance 136 | def versionCompare = (a.version <=> b.version) * -1 137 | distanceCompare == 0 ? versionCompare : distanceCompare 138 | } 139 | } else { 140 | return [version: UNKNOWN, distance: RevWalkUtils.count(walk, head, null)] 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/semver/PartialSemVerStrategy.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.semver 17 | 18 | /** 19 | * Strategy to infer portions of a semantic version. 20 | * @see SemVerStrategy 21 | */ 22 | interface PartialSemVerStrategy { 23 | /** 24 | * Infers a portion of a semantic version and returns the new state 25 | * to be used as inference continues. 26 | */ 27 | SemVerStrategyState infer(SemVerStrategyState state) 28 | } 29 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/semver/RebuildVersionStrategy.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.semver 17 | 18 | import org.ajoberstar.gradle.git.release.base.ReleasePluginExtension 19 | import org.ajoberstar.gradle.git.release.base.ReleaseVersion 20 | import org.ajoberstar.gradle.git.release.base.VersionStrategy 21 | import org.ajoberstar.grgit.Commit 22 | import org.ajoberstar.grgit.Grgit 23 | 24 | import org.gradle.api.Project 25 | import org.slf4j.Logger 26 | import org.slf4j.LoggerFactory 27 | 28 | /** 29 | * Strategy that infers the version based on the tag on the current 30 | * HEAD. 31 | */ 32 | class RebuildVersionStrategy implements VersionStrategy { 33 | private static final Logger logger = LoggerFactory.getLogger(RebuildVersionStrategy) 34 | public static final RebuildVersionStrategy INSTANCE = new RebuildVersionStrategy() 35 | 36 | private RebuildVersionStrategy() { 37 | // just hiding the constructor 38 | } 39 | 40 | @Override 41 | String getName() { 42 | return 'rebuild' 43 | } 44 | 45 | /** 46 | * Determines whether this strategy should be used to infer the version. 47 | *
    48 | *
  • Return {@code false}, if any project properties starting with "release." are set.
  • 49 | *
  • Return {@code false}, if there aren't any tags on the current HEAD that can be parsed as a version.
  • 50 | *
  • Return {@code true}, otherwise.
  • 51 | *
52 | */ 53 | @Override 54 | boolean selector(Project project, Grgit grgit) { 55 | def clean = grgit.status().clean 56 | def propsSpecified = project.hasProperty(SemVerStrategy.SCOPE_PROP) || project.hasProperty(SemVerStrategy.STAGE_PROP) 57 | def headVersion = getHeadVersion(project, grgit) 58 | 59 | if (clean && !propsSpecified && headVersion) { 60 | logger.info('Using {} strategy because repo is clean, no "release." properties found and head version is {}', name, headVersion) 61 | return true 62 | } else { 63 | logger.info('Skipping {} strategy because clean is {}, "release." properties are {} and head version is {}', name, clean, propsSpecified, headVersion) 64 | return false 65 | } 66 | } 67 | 68 | /** 69 | * Infers the version based on the version tag on the current HEAD with the 70 | * highest precendence. 71 | */ 72 | @Override 73 | ReleaseVersion infer(Project project, Grgit grgit) { 74 | String version = getHeadVersion(project, grgit) 75 | def releaseVersion = new ReleaseVersion(version, version, false) 76 | logger.debug('Inferred version {} by strategy {}', releaseVersion, name) 77 | return releaseVersion 78 | } 79 | 80 | private String getHeadVersion(Project project, Grgit grgit) { 81 | def tagStrategy = project.extensions.getByType(ReleasePluginExtension).tagStrategy 82 | Commit head = grgit.head() 83 | return grgit.tag.list().findAll { 84 | it.commit == head 85 | }.collect { 86 | tagStrategy.parseTag(it) 87 | }.findAll { 88 | it != null 89 | }.max()?.toString() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/semver/SemVerStrategy.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.semver 17 | 18 | import groovy.transform.Immutable 19 | import groovy.transform.PackageScope 20 | 21 | import com.github.zafarkhaja.semver.Version 22 | import org.ajoberstar.gradle.git.release.base.ReleasePluginExtension 23 | import org.ajoberstar.gradle.git.release.base.ReleaseVersion 24 | import org.ajoberstar.gradle.git.release.base.DefaultVersionStrategy 25 | import org.ajoberstar.grgit.Grgit 26 | 27 | import org.gradle.api.GradleException 28 | import org.gradle.api.Project 29 | 30 | import org.slf4j.Logger 31 | import org.slf4j.LoggerFactory 32 | 33 | /** 34 | * Strategy to infer versions that comply with Semantic Versioning. 35 | * @see PartialSemVerStrategy 36 | * @see SemVerStrategyState 37 | * @see Wiki Doc 38 | */ 39 | @Immutable(copyWith=true, knownImmutableClasses=[PartialSemVerStrategy]) 40 | final class SemVerStrategy implements DefaultVersionStrategy { 41 | private static final Logger logger = LoggerFactory.getLogger(SemVerStrategy) 42 | static final String SCOPE_PROP = 'release.scope' 43 | static final String STAGE_PROP = 'release.stage' 44 | 45 | /** 46 | * The name of the strategy. 47 | */ 48 | String name 49 | 50 | /** 51 | * The stages supported by this strategy. 52 | */ 53 | SortedSet stages 54 | 55 | /** 56 | * Whether or not this strategy can be used if the repo has uncommited changes. 57 | */ 58 | boolean allowDirtyRepo 59 | 60 | /** 61 | * The strategy used to infer the normal component of the version. There is no enforcement that 62 | * this strategy only modify that part of the state. 63 | */ 64 | PartialSemVerStrategy normalStrategy 65 | 66 | /** 67 | * The strategy used to infer the pre-release component of the version. There is no enforcement that 68 | * this strategy only modify that part of the state. 69 | */ 70 | PartialSemVerStrategy preReleaseStrategy 71 | 72 | /** 73 | * The strategy used to infer the build metadata component of the version. There is no enforcement that 74 | * this strategy only modify that part of the state. 75 | */ 76 | PartialSemVerStrategy buildMetadataStrategy 77 | 78 | /** 79 | * Whether or not to create tags for versions inferred by this strategy. 80 | */ 81 | boolean createTag 82 | 83 | /** 84 | * Whether or not to enforce that versions inferred by this strategy are of higher precedence 85 | * than the nearest any. 86 | */ 87 | boolean enforcePrecedence 88 | 89 | /** 90 | * Determines whether this strategy can be used to infer the version as a default. 91 | *
    92 | *
  • Return {@code false}, if the {@code release.stage} is not one listed in the {@code stages} property.
  • 93 | *
  • Return {@code false}, if the repository has uncommitted changes and {@code allowDirtyRepo} is {@code false}.
  • 94 | *
  • Return {@code true}, otherwise.
  • 95 | *
96 | */ 97 | @Override 98 | boolean defaultSelector(Project project, Grgit grgit) { 99 | String stage = getPropertyOrNull(project, STAGE_PROP) 100 | if (stage != null && !stages.contains(stage)) { 101 | logger.info('Skipping {} default strategy because stage ({}) is not one of: {}', name, stage, stages) 102 | return false 103 | } else if (!allowDirtyRepo && !grgit.status().clean) { 104 | logger.info('Skipping {} default strategy because repo is dirty.', name) 105 | return false 106 | } else { 107 | String status = grgit.status().clean ? 'clean' : 'dirty' 108 | logger.info('Using {} default strategy because repo is {} and no stage defined', name, status) 109 | return true 110 | } 111 | } 112 | 113 | /** 114 | * Determines whether this strategy should be used to infer the version. 115 | *
    116 | *
  • Return {@code false}, if the {@code release.stage} is not one listed in the {@code stages} property.
  • 117 | *
  • Return {@code false}, if the repository has uncommitted changes and {@code allowDirtyRepo} is {@code false}.
  • 118 | *
  • Return {@code true}, otherwise.
  • 119 | *
120 | */ 121 | @Override 122 | boolean selector(Project project, Grgit grgit) { 123 | String stage = getPropertyOrNull(project, STAGE_PROP) 124 | if (stage == null || !stages.contains(stage)) { 125 | logger.info('Skipping {} strategy because stage ({}) is not one of: {}', name, stage, stages) 126 | return false 127 | } else if (!allowDirtyRepo && !grgit.status().clean) { 128 | logger.info('Skipping {} strategy because repo is dirty.', name) 129 | return false 130 | } else { 131 | String status = grgit.status().clean ? 'clean' : 'dirty' 132 | logger.info('Using {} strategy because repo is {} and stage ({}) is one of: {}', name, status, stage, stages) 133 | return true 134 | } 135 | } 136 | 137 | /** 138 | * Infers the version to use for this build. Uses the normal, pre-release, and build metadata 139 | * strategies in order to infer the version. If the {@code release.stage} is not set, uses the 140 | * first value in the {@code stages} set (i.e. the one with the lowest precedence). After inferring 141 | * the version precedence will be enforced, if required by this strategy. 142 | */ 143 | @Override 144 | ReleaseVersion infer(Project project, Grgit grgit) { 145 | def tagStrategy = project.extensions.getByType(ReleasePluginExtension).tagStrategy 146 | return doInfer(project, grgit, new NearestVersionLocator(tagStrategy)) 147 | } 148 | 149 | @PackageScope 150 | ReleaseVersion doInfer(Project project, Grgit grgit, NearestVersionLocator locator) { 151 | ChangeScope scope = getPropertyOrNull(project, SCOPE_PROP).with { scope -> 152 | scope == null ? null : ChangeScope.valueOf(scope.toUpperCase()) 153 | } 154 | String stage = getPropertyOrNull(project, STAGE_PROP) ?: stages.first() 155 | if (!stages.contains(stage)) { 156 | throw new GradleException("Stage ${stage} is not one of ${stages} allowed for strategy ${name}.") 157 | } 158 | logger.info('Beginning version inference using {} strategy and input scope ({}) and stage ({})', name, scope, stage) 159 | 160 | NearestVersion nearestVersion = locator.locate(grgit) 161 | logger.debug('Located nearest version: {}', nearestVersion) 162 | 163 | SemVerStrategyState state = new SemVerStrategyState( 164 | scopeFromProp: scope, 165 | stageFromProp: stage, 166 | currentHead: grgit.head(), 167 | currentBranch: grgit.branch.current, 168 | repoDirty: !grgit.status().clean, 169 | nearestVersion: nearestVersion 170 | ) 171 | 172 | Version version = StrategyUtil.all( 173 | normalStrategy, preReleaseStrategy, buildMetadataStrategy).infer(state).toVersion() 174 | 175 | logger.warn('Inferred project: {}, version: {}', project.name, version) 176 | 177 | if (enforcePrecedence && version < nearestVersion.any) { 178 | throw new GradleException("Inferred version (${version}) cannot be lower than nearest (${nearestVersion.any}). Required by selected strategy.") 179 | } 180 | 181 | return new ReleaseVersion(version.toString(), nearestVersion.normal.toString(), createTag) 182 | } 183 | 184 | private String getPropertyOrNull(Project project, String name) { 185 | return project.hasProperty(name) ? project.property(name) : null 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/semver/SemVerStrategyState.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.semver 17 | 18 | import groovy.transform.Immutable 19 | import groovy.transform.ToString 20 | 21 | import com.github.zafarkhaja.semver.Version 22 | 23 | import org.ajoberstar.grgit.Branch 24 | import org.ajoberstar.grgit.Commit 25 | 26 | /** 27 | * Working state used by {@link PartialSemVerStrategy}. 28 | */ 29 | @Immutable(copyWith=true) 30 | @ToString(includeNames=true) 31 | final class SemVerStrategyState { 32 | ChangeScope scopeFromProp 33 | String stageFromProp 34 | Commit currentHead 35 | Branch currentBranch 36 | boolean repoDirty 37 | NearestVersion nearestVersion 38 | String inferredNormal 39 | String inferredPreRelease 40 | String inferredBuildMetadata 41 | 42 | Version toVersion() { 43 | return new Version.Builder().with { 44 | normalVersion = inferredNormal 45 | preReleaseVersion = inferredPreRelease 46 | buildMetadata =inferredBuildMetadata 47 | build() 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/semver/StrategyUtil.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.semver 17 | 18 | /** 19 | * Utility class to more easily create {@link PartialSemVerStrategy} instances. 20 | */ 21 | final class StrategyUtil { 22 | private StrategyUtil() { 23 | throw new AssertionError('Cannot instantiate this class.') 24 | } 25 | 26 | /** 27 | * Creates a strategy backed by the given closure. It should accept and return 28 | * a {@link SemVerStrategyState}. 29 | */ 30 | static final PartialSemVerStrategy closure(Closure behavior) { 31 | return new ClosureBackedPartialSemVerStrategy(behavior) 32 | } 33 | 34 | /** 35 | * Creates a strategy that applies all of the given strategies in order. 36 | */ 37 | static final PartialSemVerStrategy all(PartialSemVerStrategy... strategies) { 38 | return new ApplyAllChainedPartialSemVerStrategy(strategies as List) 39 | } 40 | 41 | /** 42 | * Creates a strategy that applies each strategy in order, until one changes 43 | * the state, which is then returned. 44 | */ 45 | static final PartialSemVerStrategy one(PartialSemVerStrategy... strategies) { 46 | return new ChooseOneChainedPartialSemVerStrategy(strategies as List) 47 | } 48 | 49 | /** 50 | * Returns the int value of a string or returns 0 if it cannot be parsed. 51 | */ 52 | static final int parseIntOrZero(String str) { 53 | try { 54 | return Integer.parseInt(str) 55 | } catch (NumberFormatException e) { 56 | return 0 57 | } 58 | } 59 | 60 | /** 61 | * Increments the nearest normal version using the specified scope. 62 | */ 63 | static final SemVerStrategyState incrementNormalFromScope(SemVerStrategyState state, ChangeScope scope) { 64 | def oldNormal = state.nearestVersion.normal 65 | switch (scope) { 66 | case ChangeScope.MAJOR: 67 | return state.copyWith(inferredNormal: oldNormal.incrementMajorVersion()) 68 | case ChangeScope.MINOR: 69 | return state.copyWith(inferredNormal: oldNormal.incrementMinorVersion()) 70 | case ChangeScope.PATCH: 71 | return state.copyWith(inferredNormal: oldNormal.incrementPatchVersion()) 72 | default: 73 | return state 74 | } 75 | } 76 | 77 | private static class ClosureBackedPartialSemVerStrategy implements PartialSemVerStrategy { 78 | private final Closure behavior 79 | 80 | ClosureBackedPartialSemVerStrategy(Closure behavior) { 81 | this.behavior = behavior 82 | } 83 | 84 | @Override 85 | SemVerStrategyState infer(SemVerStrategyState state) { 86 | return behavior(state) 87 | } 88 | } 89 | 90 | private static class ApplyAllChainedPartialSemVerStrategy implements PartialSemVerStrategy { 91 | private final List strategies 92 | 93 | ApplyAllChainedPartialSemVerStrategy(List strategies) { 94 | this.strategies = strategies 95 | } 96 | 97 | @Override 98 | SemVerStrategyState infer(SemVerStrategyState initialState) { 99 | return strategies.inject(initialState) { state, strategy -> 100 | strategy.infer(state) 101 | } 102 | } 103 | } 104 | 105 | private static class ChooseOneChainedPartialSemVerStrategy implements PartialSemVerStrategy { 106 | private final List strategies 107 | 108 | ChooseOneChainedPartialSemVerStrategy(List strategies) { 109 | this.strategies = strategies 110 | } 111 | 112 | @Override 113 | SemVerStrategyState infer(SemVerStrategyState oldState) { 114 | def result = strategies.findResult { strategy -> 115 | def newState = strategy.infer(oldState) 116 | oldState == newState ? null : newState 117 | } 118 | return result ?: oldState 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/git/release/semver/package-info.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Support for version strategies that support semantic versioning. 18 | */ 19 | package org.ajoberstar.gradle.git.release.semver 20 | -------------------------------------------------------------------------------- /src/main/groovy/org/ajoberstar/gradle/util/ObjectUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.util; 17 | 18 | import java.util.concurrent.Callable; 19 | 20 | import groovy.lang.Closure; 21 | 22 | /** 23 | * Utility class for general {@code Object} related operations. 24 | * @since 0.1.0 25 | */ 26 | public final class ObjectUtil { 27 | /** 28 | * Cannot instantiate 29 | * @throws AssertionError always 30 | */ 31 | private ObjectUtil() { 32 | throw new AssertionError("Cannot instantiate this class"); 33 | } 34 | 35 | /** 36 | * Unpacks the given object by recursively 37 | * calling the {@code call()} method if the 38 | * object is a {@code Closure} or {@code Callable}. 39 | * @param obj the object to unpack 40 | * @return the unpacked value of the object 41 | */ 42 | @SuppressWarnings("rawtypes") 43 | public static Object unpack(Object obj) { 44 | Object value = obj; 45 | while (value != null) { 46 | if (value instanceof Closure) { 47 | value = ((Closure) value).call(); 48 | } else if (value instanceof Callable) { 49 | try { 50 | value = ((Callable) value).call(); 51 | } catch (Exception e) { 52 | throw new RuntimeException(e); 53 | } 54 | } else { 55 | return value; 56 | } 57 | } 58 | return value; 59 | } 60 | 61 | /** 62 | * Unpacks the given object to its {@code String} 63 | * value. Same behavior as the other {@code unpack} 64 | * method ending with a call to {@code toString()}. 65 | * @param obj the value to unpack 66 | * @return the unpacked string value 67 | * @see ObjectUtil#unpack(Object) 68 | */ 69 | public static String unpackString(Object obj) { 70 | Object value = unpack(obj); 71 | return value == null ? null : value.toString(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/org.ajoberstar.github-pages.properties: -------------------------------------------------------------------------------- 1 | implementation-class=org.ajoberstar.gradle.git.ghpages.GithubPagesPlugin 2 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/org.ajoberstar.grgit.properties: -------------------------------------------------------------------------------- 1 | implementation-class=org.ajoberstar.gradle.git.base.GrgitPlugin 2 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/org.ajoberstar.release-base.properties: -------------------------------------------------------------------------------- 1 | implementation-class=org.ajoberstar.gradle.git.release.base.BaseReleasePlugin 2 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/org.ajoberstar.release-experimental.properties: -------------------------------------------------------------------------------- 1 | implementation-class=org.ajoberstar.gradle.git.release.experimental.ExperimentalReleasePlugin 2 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/org.ajoberstar.release-opinion.properties: -------------------------------------------------------------------------------- 1 | implementation-class=org.ajoberstar.gradle.git.release.opinion.OpinionReleasePlugin 2 | -------------------------------------------------------------------------------- /src/test/groovy/org/ajoberstar/gradle/git/ghpages/GithubPagesPluginExtensionSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.ghpages 17 | 18 | import org.ajoberstar.gradle.git.auth.BasicPasswordCredentials 19 | import org.gradle.api.Project 20 | import org.gradle.api.file.CopySpec 21 | import org.gradle.testfixtures.ProjectBuilder 22 | import spock.lang.Specification 23 | import spock.lang.Unroll 24 | 25 | class GithubPagesPluginExtensionSpec extends Specification { 26 | Project project = ProjectBuilder.builder().withProjectDir(new File('.')).build() 27 | 28 | def 'Verify the defaults'() { 29 | def ext = new GithubPagesPluginExtension(project) 30 | 31 | expect: 32 | ext.commitMessage == 'Publish of Github pages from Gradle.' 33 | ext.credentials instanceof BasicPasswordCredentials 34 | ext.pages instanceof CopySpec 35 | ext.targetBranch == 'gh-pages' 36 | ext.workingDir == project.file("${project.buildDir}/ghpages") 37 | ext.workingPath == "${project.buildDir}/ghpages" 38 | ext.deleteExistingFiles 39 | ext.pages.relativeDestinationDir == '.' 40 | // TODO ideally would check the repoUri, but need a more stable case 41 | } 42 | 43 | @Unroll 44 | def 'Repo uri [#repoUri] results in [#expected]'() { 45 | def ext = new GithubPagesPluginExtension(project) 46 | ext.repoUri = repoUri 47 | 48 | expect: 49 | ext.getRepoUri() == expected 50 | 51 | where: 52 | repoUri || expected 53 | 'git@domain.tld:user/repo.git' || 'git@domain.tld:user/repo.git' 54 | // as repoUri is an Object which gets "unpacked", I expect there are 55 | // use-cases for this. But I've none 56 | } 57 | 58 | def 'Property workingDir is based on workingPath'() { 59 | def ext = new GithubPagesPluginExtension(project) 60 | ext.workingPath = "${project.buildDir}${File.separator}some-path" 61 | 62 | when: 63 | def workingDir = ext.getWorkingDir() 64 | 65 | then: 66 | workingDir.absolutePath.endsWith(old(ext.workingPath)) 67 | } 68 | 69 | def 'Property pages.relativeDestinationDir is relative to workingDir'() { 70 | def ext = new GithubPagesPluginExtension(project) 71 | ext.workingPath = "${project.buildDir}${File.separator}some-path" 72 | ext.pages { 73 | into 'docs' 74 | } 75 | 76 | when: 77 | def workingDir = ext.workingDir 78 | def relativeDestinationDir = ext.pages.relativeDestinationDir 79 | 80 | then: 81 | workingDir.absolutePath == old(ext.workingPath) 82 | relativeDestinationDir == 'docs' 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/groovy/org/ajoberstar/gradle/git/ghpages/GithubPagesPluginSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.ghpages 17 | 18 | import org.ajoberstar.grgit.Grgit 19 | import org.gradle.api.Project 20 | import org.gradle.api.Task 21 | import org.gradle.testfixtures.ProjectBuilder 22 | import spock.lang.Specification 23 | import spock.lang.Unroll 24 | 25 | class GithubPagesPluginSpec extends Specification { 26 | public static final String PLUGIN_NAME = 'org.ajoberstar.github-pages' 27 | public static final String EXTENSION_NAME = 'githubPages' 28 | Project project = ProjectBuilder.builder().withProjectDir(new File('.')).build() 29 | 30 | def 'Creates the [githubPages] extension'() { 31 | assert !project.plugins.hasPlugin(PLUGIN_NAME) 32 | assert !project.extensions.findByName(EXTENSION_NAME) 33 | 34 | when: 35 | project.plugins.apply(PLUGIN_NAME) 36 | 37 | then: 38 | def extension = project.extensions.findByName(EXTENSION_NAME) 39 | extension instanceof GithubPagesPluginExtension 40 | } 41 | 42 | @Unroll 43 | def 'Implements the [#taskName] task'() { 44 | assert !project.plugins.hasPlugin(PLUGIN_NAME) 45 | assert !project.tasks.findByName(taskName) 46 | 47 | when: 48 | project.plugins.apply(PLUGIN_NAME) 49 | 50 | then: 51 | project.tasks.findByName(taskName) 52 | 53 | where: 54 | taskName || _ 55 | 'prepareGhPages' || _ 56 | 'publishGhPages' || _ 57 | } 58 | 59 | def '[publishGhPages] task depends on [prepareGhPages] tasks'() { 60 | project.plugins.apply(PLUGIN_NAME) 61 | 62 | when: 63 | def task = project.tasks.findByName('publishGhPages') 64 | 65 | then: 66 | task.taskDependencies.getDependencies(task).find { Task it -> 67 | it.name == 'prepareGhPages' 68 | } 69 | } 70 | 71 | @Unroll 72 | def 'Task [#taskName] has description [#description]'() { 73 | project.plugins.apply(PLUGIN_NAME) 74 | 75 | when: 76 | def task = project.tasks.findByName(taskName) 77 | 78 | then: 79 | task.description == description 80 | 81 | where: 82 | taskName || description 83 | 'prepareGhPages' || 'Prepare the gh-pages changes locally' 84 | 'publishGhPages' || 'Publishes all gh-pages changes to Github' 85 | } 86 | 87 | def '[prepareGhPages] depends on tasks add to the pages spec from'() { 88 | project.plugins.apply(PLUGIN_NAME) 89 | project.plugins.apply('java') 90 | when: 91 | project.githubPages.pages { 92 | from project.tasks.javadoc 93 | } 94 | def task = project.tasks.findByName('prepareGhPages') 95 | then: 96 | task.taskDependencies.getDependencies(task).find { it.name == 'javadoc' } 97 | } 98 | 99 | // TODO need a test for gh-pages branch creatino if didn't exist before 100 | } 101 | -------------------------------------------------------------------------------- /src/test/groovy/org/ajoberstar/gradle/git/release/base/BaseReleasePluginSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.base 17 | 18 | import org.ajoberstar.grgit.Branch 19 | import org.ajoberstar.grgit.BranchStatus 20 | import org.ajoberstar.grgit.Grgit 21 | import org.ajoberstar.grgit.service.BranchService 22 | import org.ajoberstar.grgit.service.TagService 23 | 24 | import org.gradle.api.GradleException 25 | import org.gradle.api.Project 26 | import org.gradle.testfixtures.ProjectBuilder 27 | 28 | import spock.lang.Specification 29 | 30 | class BaseReleasePluginSpec extends Specification { 31 | Project project = ProjectBuilder.builder().build() 32 | 33 | def setup() { 34 | project.plugins.apply('org.ajoberstar.release-base') 35 | } 36 | 37 | def 'prepare task succeeds if branch is up to date'() { 38 | given: 39 | Grgit repo = GroovyMock() 40 | BranchService branch = GroovyMock() 41 | repo.branch >> branch 42 | branch.current >> new Branch(fullName: 'refs/heads/master') 43 | branch.status([branch: 'refs/heads/master']) >> new BranchStatus(behindCount: 0) 44 | 45 | project.release { 46 | grgit = repo 47 | } 48 | when: 49 | project.tasks.prepare.execute() 50 | then: 51 | notThrown(GradleException) 52 | 1 * repo.fetch([remote: 'origin']) 53 | } 54 | 55 | def 'prepare task fails if branch is behind'() { 56 | given: 57 | Grgit repo = GroovyMock() 58 | BranchService branch = GroovyMock() 59 | repo.branch >> branch 60 | branch.current >> new Branch(fullName: 'refs/heads/master', trackingBranch: new Branch(fullName: 'refs/remotes/origin/master')) 61 | branch.status([branch: 'refs/heads/master']) >> new BranchStatus(behindCount: 2) 62 | 63 | project.release { 64 | grgit = repo 65 | } 66 | when: 67 | project.tasks.prepare.execute() 68 | then: 69 | thrown(GradleException) 70 | 1 * repo.fetch([remote: 'origin']) 71 | } 72 | 73 | def 'release task pushes branch and tag if created'() { 74 | given: 75 | Grgit repo = GroovyMock() 76 | BranchService branch = GroovyMock() 77 | repo.branch >> branch 78 | TagService tag = GroovyMock() 79 | repo.tag >> tag 80 | branch.current >> new Branch(fullName: 'refs/heads/master', trackingBranch: new Branch(fullName: 'refs/remotes/origin/master')) 81 | project.release { 82 | versionStrategy([ 83 | getName: { 'a' }, 84 | selector: {proj, repo2 -> true }, 85 | infer: {proj, repo2 -> new ReleaseVersion('1.2.3', null, true)}] as VersionStrategy) 86 | grgit = repo 87 | } 88 | when: 89 | project.tasks.release.execute() 90 | then: 91 | 1 * repo.push([remote: 'origin', refsOrSpecs: ['refs/heads/master', 'v1.2.3']]) 92 | } 93 | 94 | def 'release task pushes branch but not tag if it was not created'() { 95 | given: 96 | Grgit repo = GroovyMock() 97 | BranchService branch = GroovyMock() 98 | repo.branch >> branch 99 | TagService tag = GroovyMock() 100 | repo.tag >> tag 101 | branch.current >> new Branch(fullName: 'refs/heads/master', trackingBranch: new Branch(fullName: 'refs/remotes/origin/master')) 102 | project.release { 103 | versionStrategy([ 104 | getName: { 'a' }, 105 | selector: {proj, repo2 -> true }, 106 | infer: {proj, repo2 -> new ReleaseVersion('1.2.3', null, false)}] as VersionStrategy) 107 | grgit = repo 108 | } 109 | when: 110 | project.tasks.release.execute() 111 | then: 112 | 1 * repo.push([remote: 'origin', refsOrSpecs: ['refs/heads/master']]) 113 | } 114 | 115 | def 'release task skips push if on detached head'() { 116 | given: 117 | Grgit repo = GroovyMock() 118 | BranchService branch = GroovyMock() 119 | repo.branch >> branch 120 | TagService tag = GroovyMock() 121 | repo.tag >> tag 122 | branch.current >> new Branch(fullName: 'HEAD') 123 | project.release { 124 | versionStrategy([ 125 | getName: { 'a' }, 126 | selector: {proj, repo2 -> true }, 127 | infer: {proj, repo2 -> new ReleaseVersion('1.2.3', null, false)}] as VersionStrategy) 128 | grgit = repo 129 | } 130 | when: 131 | project.tasks.release.execute() 132 | then: 133 | 0 * repo.push(_) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/groovy/org/ajoberstar/gradle/git/release/base/ReleasePluginExtensionSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.base 17 | 18 | import org.ajoberstar.grgit.Grgit 19 | 20 | import org.gradle.api.GradleException 21 | import org.gradle.api.Project 22 | import org.gradle.testfixtures.ProjectBuilder 23 | 24 | import spock.lang.Specification 25 | 26 | class ReleasePluginExtensionSpec extends Specification { 27 | def 'infers default version if selector returns false for all but default'() { 28 | given: 29 | Project project = ProjectBuilder.builder().build() 30 | ReleasePluginExtension extension = new ReleasePluginExtension(project) 31 | extension.grgit = GroovyMock(Grgit) 32 | extension.versionStrategy([ 33 | getName: { 'b' }, 34 | selector: { proj, grgit -> false }, 35 | infer: { proj, grgit -> new ReleaseVersion('1.0.0', null, true) }] as VersionStrategy) 36 | extension.defaultVersionStrategy = [ 37 | getName: { 'a' }, 38 | selector: { proj, grgit -> true }, 39 | infer: { proj, grgit -> new ReleaseVersion('1.2.3', null, true) }] as VersionStrategy 40 | expect: 41 | project.version.toString() == '1.2.3' 42 | } 43 | 44 | def 'infers using first strategy selector returns true for'() { 45 | Project project = ProjectBuilder.builder().build() 46 | ReleasePluginExtension extension = new ReleasePluginExtension(project) 47 | extension.grgit = GroovyMock(Grgit) 48 | extension.versionStrategy([ 49 | getName: { 'b' }, 50 | selector: { proj, grgit -> false }, 51 | infer: { proj, grgit -> new ReleaseVersion('1.0.0', null, true) }] as VersionStrategy) 52 | extension.versionStrategy([ 53 | getName: { 'a' }, 54 | selector: { proj, grgit -> true }, 55 | infer: { proj, grgit -> new ReleaseVersion('1.2.3', null, true) }] as VersionStrategy) 56 | expect: 57 | project.version.toString() == '1.2.3' 58 | } 59 | 60 | def 'infers using first strategy selector returns true for in order'() { 61 | Project project = ProjectBuilder.builder().build() 62 | ReleasePluginExtension extension = new ReleasePluginExtension(project) 63 | extension.grgit = GroovyMock(Grgit) 64 | extension.versionStrategy([ 65 | getName: { 'b' }, 66 | selector: { proj, grgit -> true }, 67 | infer: { proj, grgit -> new ReleaseVersion('1.0.0', null, true) }] as VersionStrategy) 68 | extension.versionStrategy([ 69 | getName: { 'a' }, 70 | selector: { proj, grgit -> true }, 71 | infer: { proj, grgit -> new ReleaseVersion('1.2.3', null, true) }] as VersionStrategy) 72 | expect: 73 | project.version.toString() == '1.0.0' 74 | } 75 | 76 | def 'infer uses default if it has default selector that passes when selector doesnt'() { 77 | given: 78 | Project project = ProjectBuilder.builder().build() 79 | ReleasePluginExtension extension = new ReleasePluginExtension(project) 80 | extension.grgit = GroovyMock(Grgit) 81 | extension.versionStrategy([ 82 | getName: { 'b' }, 83 | selector: { proj, grgit -> false }, 84 | infer: { proj, grgit -> new ReleaseVersion('1.0.0', null, true) }] as VersionStrategy) 85 | extension.defaultVersionStrategy = [ 86 | getName: { 'a' }, 87 | selector: { proj, grgit -> false }, 88 | defaultSelector: { proj, grgit -> true }, 89 | infer: { proj, grgit -> new ReleaseVersion('1.2.3', null, true) }] as DefaultVersionStrategy 90 | expect: 91 | project.version.toString() == '1.2.3' 92 | } 93 | 94 | def 'infer fails if no strategy selected including the default strategy'() { 95 | given: 96 | Project project = ProjectBuilder.builder().build() 97 | ReleasePluginExtension extension = new ReleasePluginExtension(project) 98 | extension.grgit = GroovyMock(Grgit) 99 | extension.versionStrategy([ 100 | getName: { 'b' }, 101 | selector: { proj, grgit -> false }, 102 | infer: { proj, grgit -> new ReleaseVersion('1.0.0', null, true) }] as VersionStrategy) 103 | extension.defaultVersionStrategy = [ 104 | getName: { 'a' }, 105 | selector: { proj, grgit -> false }, 106 | infer: { proj, grgit -> new ReleaseVersion('1.2.3', null, true) }] as VersionStrategy 107 | when: 108 | project.version.toString() 109 | then: 110 | thrown(GradleException) 111 | } 112 | 113 | def 'infer fails if no strategy selected and no default set'() { 114 | Project project = ProjectBuilder.builder().build() 115 | ReleasePluginExtension extension = new ReleasePluginExtension(project) 116 | extension.grgit = GroovyMock(Grgit) 117 | extension.versionStrategy([ 118 | getName: { 'b' }, 119 | selector: { proj, grgit -> false }, 120 | infer: { proj, grgit -> new ReleaseVersion('1.0.0', null, true) }] as VersionStrategy) 121 | extension.versionStrategy([ 122 | getName: { 'a' }, 123 | selector: { proj, grgit -> false }, 124 | infer: { proj, grgit -> new ReleaseVersion('1.2.3', null, true) }] as VersionStrategy) 125 | when: 126 | project.version.toString() 127 | then: 128 | thrown(GradleException) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/test/groovy/org/ajoberstar/gradle/git/release/base/TagStrategySpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.base 17 | 18 | import org.ajoberstar.grgit.Grgit 19 | import org.ajoberstar.grgit.service.TagService 20 | import spock.lang.Specification 21 | 22 | class TagStrategySpec extends Specification { 23 | def 'maybeCreateTag with version create tag true will create a tag'() { 24 | given: 25 | Grgit grgit = GroovyMock() 26 | TagService tag = GroovyMock() 27 | grgit.tag >> tag 28 | 1 * tag.add([name: 'v1.2.3', message: 'Release of 1.2.3']) 29 | 0 * tag._ 30 | expect: 31 | new TagStrategy().maybeCreateTag(grgit, new ReleaseVersion('1.2.3', null, true)) == 'v1.2.3' 32 | } 33 | 34 | def 'maybeCreateTag with version create tag false does not create a tag'() { 35 | given: 36 | Grgit grgit = GroovyMock() 37 | TagService tag = GroovyMock() 38 | grgit.tag >> tag 39 | 0 * tag._ 40 | expect: 41 | new TagStrategy().maybeCreateTag(grgit, new ReleaseVersion('1.2.3', null, false)) == null 42 | } 43 | 44 | def 'maybeCreateTag with version create tag true and prefix name with v false will create a tag'() { 45 | given: 46 | Grgit grgit = GroovyMock() 47 | TagService tag = GroovyMock() 48 | grgit.tag >> tag 49 | 1 * tag.add([name: '1.2.3', message: 'Release of 1.2.3']) 50 | 0 * tag._ 51 | def strategy = new TagStrategy() 52 | strategy.prefixNameWithV = false 53 | expect: 54 | strategy.maybeCreateTag(grgit, new ReleaseVersion('1.2.3', null, true)) == '1.2.3' 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/groovy/org/ajoberstar/gradle/git/release/opinion/OpinionReleasePluginSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.opinion 17 | 18 | import org.ajoberstar.gradle.git.release.base.ReleaseVersion 19 | import org.ajoberstar.gradle.git.release.semver.RebuildVersionStrategy 20 | import org.ajoberstar.grgit.Commit 21 | import org.ajoberstar.grgit.Grgit 22 | import org.ajoberstar.grgit.exception.GrgitException 23 | import org.ajoberstar.grgit.service.ResolveService 24 | 25 | import org.gradle.api.Project 26 | import org.gradle.testfixtures.ProjectBuilder 27 | 28 | import spock.lang.Specification 29 | 30 | class OpinionReleasePluginSpec extends Specification { 31 | Project project = ProjectBuilder.builder().build() 32 | ReleaseVersion version = new ReleaseVersion(version: '1.2.3', previousVersion: '1.2.2') 33 | 34 | def 'plugin adds correct strategies'() { 35 | given: 36 | project.plugins.apply('org.ajoberstar.release-opinion') 37 | expect: 38 | project.release.versionStrategies == [RebuildVersionStrategy.INSTANCE, Strategies.DEVELOPMENT, Strategies.PRE_RELEASE, Strategies.FINAL] 39 | project.release.defaultVersionStrategy == Strategies.DEVELOPMENT 40 | } 41 | 42 | def 'plugin tag strategy creates correct message if previous tag exists'() { 43 | given: 44 | project.plugins.apply('org.ajoberstar.release-opinion') 45 | Grgit grgit = GroovyMock() 46 | project.release.grgit = grgit 47 | ResolveService resolve = Mock() 48 | (1.._) * grgit.resolve >> resolve 49 | 1 * resolve.toCommit('v1.2.2^{commit}') >> new Commit(shortMessage: 'Commit 1') 50 | 1 * grgit.log([includes: ['HEAD'], excludes: ['v1.2.2^{commit}']]) >> [ 51 | new Commit(shortMessage: 'Commit 2'), 52 | new Commit(shortMessage: 'Next commit')] 53 | 0 * grgit._ 54 | expect: 55 | project.release.tagStrategy.generateMessage(version).trim() == ''' 56 | Release of 1.2.3 57 | 58 | - Commit 2 59 | - Next commit 60 | '''.trim() 61 | } 62 | 63 | def 'plugin tag strategy creates correct message if previous tag exists and no prefix with v'() { 64 | given: 65 | project.plugins.apply('org.ajoberstar.release-opinion') 66 | Grgit grgit = GroovyMock() 67 | project.release.grgit = grgit 68 | project.release.tagStrategy.prefixNameWithV = false 69 | ResolveService resolve = Mock() 70 | (1.._) * grgit.resolve >> resolve 71 | 1 * resolve.toCommit('1.2.2^{commit}') >> new Commit(shortMessage: 'Commit 1') 72 | 1 * grgit.log([includes: ['HEAD'], excludes: ['1.2.2^{commit}']]) >> [ 73 | new Commit(shortMessage: 'Commit 2'), 74 | new Commit(shortMessage: 'Next commit')] 75 | 0 * grgit._ 76 | expect: 77 | project.release.tagStrategy.generateMessage(version).trim() == ''' 78 | Release of 1.2.3 79 | 80 | - Commit 2 81 | - Next commit 82 | '''.trim() 83 | } 84 | 85 | def 'plugin tag strategy creates correct message if previous tag does not exist'() { 86 | given: 87 | project.plugins.apply('org.ajoberstar.release-opinion') 88 | Grgit grgit = GroovyMock() 89 | project.release.grgit = grgit 90 | ResolveService resolve = Mock() 91 | (1.._) * grgit.resolve >> resolve 92 | 1 * grgit.resolve.toCommit('v1.2.2^{commit}') >> { throw new GrgitException('fail') } 93 | 1 * grgit.log([includes: ['HEAD'], excludes: []]) >> [ 94 | new Commit(shortMessage: 'Commit 1'), 95 | new Commit(shortMessage: 'Commit 2'), 96 | new Commit(shortMessage: 'Next commit')] 97 | 0 * grgit._ 98 | expect: 99 | project.release.tagStrategy.generateMessage(version).trim() == ''' 100 | Release of 1.2.3 101 | 102 | - Commit 1 103 | - Commit 2 104 | - Next commit 105 | '''.trim() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/test/groovy/org/ajoberstar/gradle/git/release/opinion/StrategiesSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.opinion 17 | 18 | import com.github.zafarkhaja.semver.Version 19 | 20 | import org.ajoberstar.gradle.git.release.base.ReleaseVersion 21 | import org.ajoberstar.gradle.git.release.semver.ChangeScope 22 | import org.ajoberstar.gradle.git.release.semver.NearestVersion 23 | import org.ajoberstar.gradle.git.release.semver.NearestVersionLocator 24 | import org.ajoberstar.gradle.git.release.semver.SemVerStrategyState 25 | import org.ajoberstar.gradle.git.release.semver.StrategyUtil 26 | import org.ajoberstar.grgit.Branch 27 | import org.ajoberstar.grgit.Commit 28 | import org.ajoberstar.grgit.Grgit 29 | import org.ajoberstar.grgit.Status 30 | import org.ajoberstar.grgit.service.BranchService 31 | 32 | import org.gradle.api.GradleException 33 | import org.gradle.api.Project 34 | 35 | import spock.lang.Specification 36 | import spock.lang.Unroll 37 | 38 | class StrategiesSpec extends Specification { 39 | SemVerStrategyState initialState = new SemVerStrategyState([:]) 40 | @Unroll('Normal.USE_SCOPE_PROP with scope #scope results in #version') 41 | def 'Normal.USE_SCOPE_PROP'() { 42 | def nearestVersion = new NearestVersion(normal: Version.valueOf('1.2.3')) 43 | def initialState = new SemVerStrategyState( 44 | scopeFromProp: scope, 45 | nearestVersion: nearestVersion) 46 | expect: 47 | Strategies.Normal.USE_SCOPE_PROP.infer(initialState) == 48 | new SemVerStrategyState( 49 | scopeFromProp: scope, 50 | nearestVersion: nearestVersion, 51 | inferredNormal: version) 52 | where: 53 | scope | version 54 | ChangeScope.PATCH | '1.2.4' 55 | ChangeScope.MINOR | '1.3.0' 56 | ChangeScope.MAJOR | '2.0.0' 57 | } 58 | 59 | def 'Normal.USE_NEAREST_ANY with different nearest any and normal uses any\'s normal'() { 60 | given: 61 | def nearestVersion = new NearestVersion( 62 | any: Version.valueOf('1.2.5-beta.1'), 63 | normal: Version.valueOf('1.2.4')) 64 | def initialState = new SemVerStrategyState(nearestVersion: nearestVersion) 65 | expect: 66 | Strategies.Normal.USE_NEAREST_ANY.infer(initialState) == 67 | new SemVerStrategyState(nearestVersion: nearestVersion, inferredNormal: '1.2.5') 68 | } 69 | 70 | 71 | def 'Normal.USE_NEAREST_ANY with same nearest any and normal does nothing'() { 72 | given: 73 | def initialState = new SemVerStrategyState( 74 | nearestVersion: new NearestVersion( 75 | any: Version.valueOf('1.2.4'), 76 | normal: Version.valueOf('1.2.4'))) 77 | expect: 78 | Strategies.Normal.USE_NEAREST_ANY.infer(initialState) == initialState 79 | } 80 | 81 | def 'Normal.ENFORCE_BRANCH_MAJOR_X fails if nearest normal can\'t be incremented to something that complies with branch'() { 82 | given: 83 | def initialState = new SemVerStrategyState( 84 | nearestVersion: new NearestVersion(normal: Version.valueOf('1.2.3')), 85 | currentBranch: new Branch(fullName: 'refs/heads/3.x')) 86 | when: 87 | Strategies.Normal.ENFORCE_BRANCH_MAJOR_X.infer(initialState) 88 | then: 89 | thrown(GradleException) 90 | where: 91 | version << ['1.2.3', '3.1.0', '4.0.0'] 92 | } 93 | 94 | def 'Normal.ENFORCE_BRANCH_MAJOR_X correctly increments version to comply with branch'() { 95 | given: 96 | def initialState = new SemVerStrategyState( 97 | nearestVersion: new NearestVersion(normal: Version.valueOf(nearest)), 98 | currentBranch: new Branch(fullName: 'refs/heads/3.x')) 99 | expect: 100 | Strategies.Normal.ENFORCE_BRANCH_MAJOR_X.infer(initialState) == 101 | initialState.copyWith(inferredNormal: inferred) 102 | where: 103 | nearest | inferred 104 | '2.0.0' | '3.0.0' 105 | '2.3.0' | '3.0.0' 106 | '2.3.4' | '3.0.0' 107 | '3.0.0' | null 108 | '3.1.0' | null 109 | '3.1.2' | null 110 | } 111 | 112 | def 'Normal.ENFORCE_GITFLOW_BRANCH_MAJOR_X correctly increments version to comply with branch'() { 113 | given: 114 | def initialState = new SemVerStrategyState( 115 | nearestVersion: new NearestVersion(normal: Version.valueOf(nearest)), 116 | currentBranch: new Branch(fullName: "refs/heads/${branch}")) 117 | expect: 118 | Strategies.Normal.ENFORCE_GITFLOW_BRANCH_MAJOR_X.infer(initialState) == 119 | initialState.copyWith(inferredNormal: inferred) 120 | where: 121 | branch | nearest | inferred 122 | 'release/3.x' | '2.0.0' | '3.0.0' 123 | 'release-3.x' | '2.3.4' | '3.0.0' 124 | 'release-3.x' | '3.0.0' | null 125 | 'release/3.x' | '3.1.0' | null 126 | } 127 | 128 | def 'Normal.ENFORCE_BRANCH_MAJOR_MINOR_X fails if nearest normal can\'t be incremented to something that complies with branch'() { 129 | given: 130 | def initialState = new SemVerStrategyState( 131 | nearestVersion: new NearestVersion(normal: Version.valueOf(version)), 132 | currentBranch: new Branch(fullName: 'refs/heads/3.2.x')) 133 | when: 134 | Strategies.Normal.ENFORCE_BRANCH_MAJOR_MINOR_X.infer(initialState) 135 | then: 136 | thrown(GradleException) 137 | where: 138 | version << ['1.2.3', '3.0.0', '3.3.0', '4.2.0'] 139 | } 140 | 141 | def 'Normal.ENFORCE_BRANCH_MAJOR_MINOR_X correctly increments version to comply with branch'() { 142 | given: 143 | def initialState = new SemVerStrategyState( 144 | nearestVersion: new NearestVersion(normal: Version.valueOf(nearest)), 145 | currentBranch: new Branch(fullName: "refs/heads/${branch}")) 146 | expect: 147 | Strategies.Normal.ENFORCE_BRANCH_MAJOR_MINOR_X.infer(initialState) == 148 | initialState.copyWith(inferredNormal: inferred) 149 | where: 150 | branch | nearest | inferred 151 | '3.0.x' | '2.0.0' | '3.0.0' 152 | '3.0.x' | '2.1.0' | '3.0.0' 153 | '3.0.x' | '2.1.2' | '3.0.0' 154 | '3.0.x' | '3.0.0' | '3.0.1' 155 | '3.0.x' | '3.0.1' | '3.0.2' 156 | '3.2.x' | '3.1.0' | '3.2.0' 157 | '3.2.x' | '3.1.2' | '3.2.0' 158 | '3.2.x' | '3.2.0' | '3.2.1' 159 | '3.2.x' | '3.2.1' | '3.2.2' 160 | } 161 | 162 | def 'Normal.ENFORCE_GITFLOW_BRANCH_MAJOR_MINOR_X correctly increments version to comply with branch'() { 163 | given: 164 | def initialState = new SemVerStrategyState( 165 | nearestVersion: new NearestVersion(normal: Version.valueOf(nearest)), 166 | currentBranch: new Branch(fullName: "refs/heads/${branch}")) 167 | expect: 168 | Strategies.Normal.ENFORCE_GITFLOW_BRANCH_MAJOR_MINOR_X.infer(initialState) == 169 | initialState.copyWith(inferredNormal: inferred) 170 | where: 171 | branch | nearest | inferred 172 | 'release/3.0.x' | '2.0.0' | '3.0.0' 173 | 'release-3.0.x' | '3.0.0' | '3.0.1' 174 | 'release-3.2.x' | '3.1.2' | '3.2.0' 175 | 'release/3.2.x' | '3.2.1' | '3.2.2' 176 | } 177 | 178 | def 'Normal.ENFORCE_BRANCH_MAJOR_MINOR_X correctly increments version to comply with branch if normalStrategy is to increment minor instead of patch'() { 179 | given: 180 | def initialState = new SemVerStrategyState( 181 | nearestVersion: new NearestVersion(normal: Version.valueOf(nearest)), 182 | currentBranch: new Branch(fullName: "refs/heads/${branch}")) 183 | def strategy = StrategyUtil.one(Strategies.Normal.ENFORCE_BRANCH_MAJOR_MINOR_X, Strategies.Normal.useScope(ChangeScope.MINOR)) 184 | expect: 185 | strategy.infer(initialState) == initialState.copyWith(inferredNormal: inferred) 186 | where: 187 | branch | nearest | inferred 188 | '3.0.x' | '2.0.0' | '3.0.0' 189 | '3.0.x' | '2.1.0' | '3.0.0' 190 | '3.0.x' | '2.1.2' | '3.0.0' 191 | '3.0.x' | '3.0.0' | '3.0.1' 192 | '3.0.x' | '3.0.1' | '3.0.2' 193 | '3.2.x' | '3.1.0' | '3.2.0' 194 | '3.2.x' | '3.1.2' | '3.2.0' 195 | '3.2.x' | '3.2.0' | '3.2.1' 196 | '3.2.x' | '3.2.1' | '3.2.2' 197 | } 198 | 199 | def 'Normal.ENFORCE_GITFLOW_BRANCH_MAJOR_MINOR_X correctly increments version to comply with branch if normalStrategy is to increment minor instead of patch'() { 200 | given: 201 | def initialState = new SemVerStrategyState( 202 | nearestVersion: new NearestVersion(normal: Version.valueOf(nearest)), 203 | currentBranch: new Branch(fullName: "refs/heads/${branch}")) 204 | def strategy = StrategyUtil.one(Strategies.Normal.ENFORCE_GITFLOW_BRANCH_MAJOR_MINOR_X, Strategies.Normal.useScope(ChangeScope.MINOR)) 205 | expect: 206 | strategy.infer(initialState) == initialState.copyWith(inferredNormal: inferred) 207 | where: 208 | branch | nearest | inferred 209 | 'release/3.0.x' | '2.0.0' | '3.0.0' 210 | 'release-3.0.x' | '3.0.0' | '3.0.1' 211 | 'release-3.2.x' | '3.1.2' | '3.2.0' 212 | 'release/3.2.x' | '3.2.1' | '3.2.2' 213 | } 214 | 215 | def 'Normal.useScope correctly increments normal'() { 216 | given: 217 | def initialState = new SemVerStrategyState( 218 | nearestVersion: new NearestVersion(normal: Version.valueOf('1.2.3'))) 219 | expect: 220 | Strategies.Normal.useScope(scope).infer(initialState) == initialState.copyWith(inferredNormal: inferred) 221 | where: 222 | scope | inferred 223 | ChangeScope.PATCH | '1.2.4' 224 | ChangeScope.MINOR | '1.3.0' 225 | ChangeScope.MAJOR | '2.0.0' 226 | } 227 | 228 | def 'PreRelease.NONE does nothing'() { 229 | expect: 230 | Strategies.PreRelease.NONE.infer(new SemVerStrategyState([:])) == new SemVerStrategyState([:]) 231 | } 232 | 233 | def 'PreRelease.STAGE_FIXED always replaces inferredPreRelease with stageFromProp'() { 234 | given: 235 | def initialState = new SemVerStrategyState( 236 | stageFromProp: 'boom', 237 | inferredPreRelease: initialPreRelease) 238 | expect: 239 | Strategies.PreRelease.STAGE_FIXED.infer(initialState) == initialState.copyWith(inferredPreRelease: 'boom') 240 | where: 241 | initialPreRelease << [null, 'other'] 242 | } 243 | 244 | def 'PreRelease.STAGE_FLOAT will append the stageFromProp to the nearest any\'s pre release, if any, unless it has higher precedence'() { 245 | given: 246 | def initialState = new SemVerStrategyState( 247 | stageFromProp: 'boom', 248 | inferredNormal: '1.1.0', 249 | inferredPreRelease: 'other', 250 | nearestVersion: new NearestVersion( 251 | normal: Version.valueOf('1.0.0'), 252 | any: Version.valueOf(nearest))) 253 | expect: 254 | Strategies.PreRelease.STAGE_FLOAT.infer(initialState) == initialState.copyWith(inferredPreRelease: expected) 255 | where: 256 | nearest | expected 257 | '1.0.0' | 'boom' 258 | '1.0.1-cat.something.else' | 'boom' 259 | '1.1.0-and.1' | 'boom' 260 | '1.1.0-cat.1' | 'cat.1.boom' 261 | '1.1.0-cat.something.else' | 'cat.something.else.boom' 262 | } 263 | 264 | def 'PreRelease.COUNT_INCREMENTED will increment the nearest any\'s pre release or set to 1 if not found'() { 265 | given: 266 | def initialState = new SemVerStrategyState( 267 | inferredNormal: '1.1.0', 268 | inferredPreRelease: initialPreRelease, 269 | nearestVersion: new NearestVersion( 270 | normal: Version.valueOf('1.0.0'), 271 | any: Version.valueOf(nearestAny))) 272 | expect: 273 | Strategies.PreRelease.COUNT_INCREMENTED.infer(initialState) == initialState.copyWith(inferredPreRelease: expected) 274 | where: 275 | nearestAny | initialPreRelease | expected 276 | '1.0.0' | null | '1' 277 | '1.0.0' | 'other' | 'other.1' 278 | '1.0.1-beta.1' | 'other' | 'other.1' 279 | '2.0.0-beta.1' | 'other' | 'other.1' 280 | '1.1.0-beta.1' | 'other' | 'other.1' 281 | '1.1.0-beta.1' | 'beta' | 'beta.2' 282 | '1.1.0-beta.99' | 'beta' | 'beta.100' 283 | '1.1.0-beta' | 'beta' | 'beta.1' 284 | '1.1.0-beta.1' | 'beta.1.alpha' | 'beta.1.alpha.1' 285 | '1.1.0-beta.1' | 'beta.1.alpha' | 'beta.1.alpha.1' 286 | '1.1.0-beta.2.alpha' | 'beta' | 'beta.3' 287 | } 288 | 289 | def 'PreRelease.COUNT_COMMITS_SINCE_ANY will append distanceFromAny'() { 290 | given: 291 | def initialState = new SemVerStrategyState( 292 | inferredPreRelease: initialPreRelease, 293 | nearestVersion: new NearestVersion(distanceFromAny: distance)) 294 | expect: 295 | Strategies.PreRelease.COUNT_COMMITS_SINCE_ANY.infer(initialState) == initialState.copyWith(inferredPreRelease: expected) 296 | where: 297 | initialPreRelease | distance | expected 298 | null | 0 | '0' 299 | null | 54 | '54' 300 | 'other' | 0 | 'other.0' 301 | 'other' | 54 | 'other.54' 302 | } 303 | 304 | def 'PreRelease.SHOW_UNCOMMITTED appends uncommitted only if repo is dirty'() { 305 | given: 306 | def initialState = new SemVerStrategyState( 307 | inferredPreRelease: initialPreRelease, 308 | repoDirty: dirty) 309 | expect: 310 | Strategies.PreRelease.SHOW_UNCOMMITTED.infer(initialState) == initialState.copyWith(inferredPreRelease: expected) 311 | where: 312 | initialPreRelease | dirty | expected 313 | null | false | null 314 | null | true | 'uncommitted' 315 | 'other' | false | 'other' 316 | 'other' | true | 'other.uncommitted' 317 | } 318 | 319 | def 'BuildMetadata.NONE does nothing'() { 320 | expect: 321 | Strategies.BuildMetadata.NONE.infer(new SemVerStrategyState([:])) == new SemVerStrategyState([:]) 322 | } 323 | 324 | def 'BuildMetadata.COMMIT_ABBREVIATED_ID uses current HEAD\'s abbreviated id'() { 325 | given: 326 | def initialState = new SemVerStrategyState(currentHead: new Commit(id: '5e9b2a1e98b5670a90a9ed382a35f0d706d5736c')) 327 | expect: 328 | Strategies.BuildMetadata.COMMIT_ABBREVIATED_ID.infer(initialState) == 329 | initialState.copyWith(inferredBuildMetadata: '5e9b2a1') 330 | } 331 | 332 | def 'BuildMetadata.COMMIT_FULL_ID uses current HEAD\'s abbreviated id'() { 333 | given: 334 | def id = '5e9b2a1e98b5670a90a9ed382a35f0d706d5736c' 335 | def initialState = new SemVerStrategyState(currentHead: new Commit(id: id)) 336 | expect: 337 | Strategies.BuildMetadata.COMMIT_FULL_ID.infer(initialState) == 338 | initialState.copyWith(inferredBuildMetadata: id) 339 | } 340 | 341 | def 'BuildMetadata.TIMESTAMP uses current time'() { 342 | expect: 343 | def newState = Strategies.BuildMetadata.TIMESTAMP.infer(new SemVerStrategyState([:])) 344 | def metadata = newState.inferredBuildMetadata 345 | metadata ==~ /\d{4}\.\d{2}\.\d{2}\.\d{2}\.\d{2}\.\d{2}/ 346 | newState == new SemVerStrategyState(inferredBuildMetadata: metadata) 347 | } 348 | 349 | def 'SNAPSHOT works as expected'() { 350 | given: 351 | def project = mockProject(scope, stage) 352 | def grgit = mockGrgit(repoDirty) 353 | def locator = mockLocator(nearestNormal, nearestAny) 354 | expect: 355 | Strategies.SNAPSHOT.doInfer(project, grgit, locator) == new ReleaseVersion(expected, nearestNormal, false) 356 | where: 357 | scope | stage | nearestNormal | nearestAny | repoDirty | expected 358 | null | null | '1.0.0' | '1.0.0' | false | '1.0.1-SNAPSHOT' 359 | null | null | '1.0.0' | '1.0.0' | true | '1.0.1-SNAPSHOT' 360 | null | 'SNAPSHOT' | '1.0.0' | '1.1.0-beta' | true | '1.1.0-SNAPSHOT' 361 | null | 'SNAPSHOT' | '1.0.0' | '1.1.0-zed' | true | '1.1.0-SNAPSHOT' 362 | 'PATCH' | 'SNAPSHOT' | '1.0.0' | '1.1.0-zed' | true | '1.0.1-SNAPSHOT' 363 | 'MINOR' | 'SNAPSHOT' | '1.0.0' | '1.1.0-zed' | true | '1.1.0-SNAPSHOT' 364 | 'MAJOR' | 'SNAPSHOT' | '1.0.0' | '1.1.0-zed' | true | '2.0.0-SNAPSHOT' 365 | } 366 | 367 | def 'DEVELOPMENT works as expected'() { 368 | given: 369 | def project = mockProject(scope, stage) 370 | def grgit = mockGrgit(repoDirty) 371 | def locator = mockLocator(nearestNormal, nearestAny) 372 | expect: 373 | Strategies.DEVELOPMENT.doInfer(project, grgit, locator) == new ReleaseVersion(expected, nearestNormal, false) 374 | where: 375 | scope | stage | nearestNormal | nearestAny | repoDirty | expected 376 | null | null | '1.0.0' | '1.0.0' | false | '1.0.1-dev.2+5e9b2a1' 377 | null | null | '1.0.0' | '1.0.0' | true | '1.0.1-dev.2.uncommitted+5e9b2a1' 378 | null | null | '1.0.0' | '1.1.0-alpha.1' | false | '1.1.0-dev.2+5e9b2a1' 379 | null | null | '1.0.0' | '1.1.0-rc.3' | false | '1.1.0-rc.3.dev.2+5e9b2a1' 380 | null | null | '1.0.0' | '1.1.0-rc.3' | true | '1.1.0-rc.3.dev.2.uncommitted+5e9b2a1' 381 | 'PATCH' | 'dev' | '1.0.0' | '1.0.0' | false | '1.0.1-dev.2+5e9b2a1' 382 | 'MINOR' | 'dev' | '1.0.0' | '1.0.0' | false | '1.1.0-dev.2+5e9b2a1' 383 | 'MAJOR' | 'dev' | '1.0.0' | '1.0.0' | false | '2.0.0-dev.2+5e9b2a1' 384 | } 385 | 386 | def 'PRE_RELEASE works as expected'() { 387 | def project = mockProject(scope, stage) 388 | def grgit = mockGrgit(repoDirty) 389 | def locator = mockLocator(nearestNormal, nearestAny) 390 | expect: 391 | Strategies.PRE_RELEASE.doInfer(project, grgit, locator) == new ReleaseVersion(expected, nearestNormal, true) 392 | where: 393 | scope | stage | nearestNormal | nearestAny | repoDirty | expected 394 | null | null | '1.0.0' | '1.0.0' | false | '1.0.1-milestone.1' 395 | null | 'milestone' | '1.0.0' | '1.0.0' | false | '1.0.1-milestone.1' 396 | null | 'rc' | '1.0.0' | '1.0.0' | false | '1.0.1-rc.1' 397 | 'PATCH' | 'milestone' | '1.0.0' | '1.0.0' | false | '1.0.1-milestone.1' 398 | 'MINOR' | 'milestone' | '1.0.0' | '1.0.0' | false | '1.1.0-milestone.1' 399 | 'MAJOR' | 'milestone' | '1.0.0' | '1.0.0' | false | '2.0.0-milestone.1' 400 | null | 'rc' | '1.0.0' | '1.1.0-milestone.1' | false | '1.1.0-rc.1' 401 | null | 'milestone' | '1.0.0' | '1.1.0-milestone.1' | false | '1.1.0-milestone.2' 402 | null | 'rc' | '1.0.0' | '1.1.0-rc' | false | '1.1.0-rc.1' 403 | null | 'rc' | '1.0.0' | '1.1.0-rc.4.dev.1' | false | '1.1.0-rc.5' 404 | } 405 | 406 | def 'Strategies.FINAL works as expected'() { 407 | def project = mockProject(scope, stage) 408 | def grgit = mockGrgit(repoDirty) 409 | def locator = mockLocator(nearestNormal, nearestAny) 410 | expect: 411 | Strategies.FINAL.doInfer(project, grgit, locator) == new ReleaseVersion(expected, nearestNormal, true) 412 | where: 413 | scope | stage | nearestNormal | nearestAny | repoDirty | expected 414 | null | null | '1.0.0' | '1.0.0' | false | '1.0.1' 415 | 'PATCH' | null | '1.0.0' | '1.0.0' | false | '1.0.1' 416 | 'MINOR' | null | '1.0.0' | '1.0.0' | false | '1.1.0' 417 | 'MAJOR' | null | '1.0.0' | '1.0.0' | false | '2.0.0' 418 | 'MAJOR' | 'final' | '1.0.0' | '1.0.0' | false | '2.0.0' 419 | 'MINOR' | 'final' | '1.0.0' | '1.1.0-alpha.2' | false | '1.1.0' 420 | } 421 | 422 | def 'PRE_RELEASE_ALPHA_BETA works as expected'() { 423 | def project = mockProject(scope, stage) 424 | def grgit = mockGrgit(repoDirty) 425 | def locator = mockLocator(nearestNormal, nearestAny) 426 | expect: 427 | Strategies.PRE_RELEASE_ALPHA_BETA.doInfer(project, grgit, locator) == new ReleaseVersion(expected, nearestNormal, true) 428 | where: 429 | scope | stage | nearestNormal | nearestAny | repoDirty | expected 430 | null | null | '1.0.0' | '1.0.0' | false | '1.0.1-alpha.1' 431 | null | 'alpha' | '1.0.0' | '1.0.0' | false | '1.0.1-alpha.1' 432 | null | 'beta' | '1.0.0' | '1.0.0' | false | '1.0.1-beta.1' 433 | null | 'rc' | '1.0.0' | '1.0.0' | false | '1.0.1-rc.1' 434 | 'PATCH' | 'alpha' | '1.0.0' | '1.0.0' | false | '1.0.1-alpha.1' 435 | 'MINOR' | 'alpha' | '1.0.0' | '1.0.0' | false | '1.1.0-alpha.1' 436 | 'MAJOR' | 'alpha' | '1.0.0' | '1.0.0' | false | '2.0.0-alpha.1' 437 | 'PATCH' | 'beta' | '1.0.0' | '1.0.0' | false | '1.0.1-beta.1' 438 | 'MINOR' | 'beta' | '1.0.0' | '1.0.0' | false | '1.1.0-beta.1' 439 | 'MAJOR' | 'beta' | '1.0.0' | '1.0.0' | false | '2.0.0-beta.1' 440 | null | 'rc' | '1.0.0' | '1.1.0-beta.1' | false | '1.1.0-rc.1' 441 | null | 'beta' | '1.0.0' | '1.1.0-beta.1' | false | '1.1.0-beta.2' 442 | null | 'rc' | '1.0.0' | '1.1.0-rc' | false | '1.1.0-rc.1' 443 | null | 'rc' | '1.0.0' | '1.1.0-rc.4.dev.1' | false | '1.1.0-rc.5' 444 | } 445 | 446 | def mockProject(String scope, String stage) { 447 | Project project = Mock() 448 | 449 | project.hasProperty('release.scope') >> (scope as boolean) 450 | project.property('release.scope') >> scope 451 | 452 | project.hasProperty('release.stage') >> (stage as boolean) 453 | project.property('release.stage') >> stage 454 | 455 | return project 456 | } 457 | 458 | def mockGrgit(boolean repoDirty, String branchName = 'master') { 459 | Grgit grgit = GroovyMock() 460 | 461 | Status status = Mock() 462 | status.clean >> !repoDirty 463 | grgit.status() >> status 464 | 465 | grgit.head() >> new Commit(id: '5e9b2a1e98b5670a90a9ed382a35f0d706d5736c') 466 | 467 | BranchService branch = GroovyMock() 468 | branch.current >> new Branch(fullName: "refs/heads/${branchName}") 469 | grgit.branch >> branch 470 | 471 | return grgit 472 | } 473 | 474 | def mockLocator(String nearestNormal, String nearestAny) { 475 | NearestVersionLocator locator = Mock() 476 | locator.locate(_) >> new NearestVersion( 477 | normal: Version.valueOf(nearestNormal), 478 | distanceFromNormal: 5, 479 | any: Version.valueOf(nearestAny), 480 | distanceFromAny: 2 481 | ) 482 | return locator 483 | } 484 | } 485 | -------------------------------------------------------------------------------- /src/test/groovy/org/ajoberstar/gradle/git/release/semver/NearestVersionLocatorSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.semver 17 | 18 | import com.github.zafarkhaja.semver.Version 19 | import org.ajoberstar.gradle.git.release.base.TagStrategy 20 | import org.ajoberstar.grgit.Grgit 21 | import spock.lang.Shared 22 | import spock.lang.Specification 23 | import spock.lang.Unroll 24 | 25 | import java.nio.file.Files 26 | import java.security.SecureRandom 27 | 28 | class NearestVersionLocatorSpec extends Specification { 29 | @Shared File repoDir 30 | 31 | @Shared Grgit grgit 32 | 33 | @Shared SecureRandom random = new SecureRandom() 34 | 35 | def setupSpec() { 36 | repoDir = Files.createTempDirectory('repo').toFile() 37 | grgit = Grgit.init(dir: repoDir) 38 | 39 | commit() 40 | commit() 41 | addBranch('unreachable') 42 | 43 | commit() 44 | addTag('0.0.1-beta.3') 45 | addBranch('no-normal') 46 | 47 | commit() 48 | addTag('0.1.0') 49 | 50 | commit() 51 | addBranch('RB_0.1') 52 | 53 | commit() 54 | commit() 55 | addTag('0.2.0') 56 | 57 | checkout('RB_0.1') 58 | 59 | commit() 60 | addTag('v0.1.1+2010.01.01.12.00.00') 61 | 62 | commit() 63 | commit() 64 | commit() 65 | commit() 66 | addTag('v0.1.2-beta.1') 67 | addTag('v0.1.2-alpha.1') 68 | addTag('not-a-version') 69 | 70 | commit() 71 | commit() 72 | commit() 73 | checkout('master') 74 | 75 | commit() 76 | addTag('v1.0.0') 77 | addTag('v1.0.0-rc.3') 78 | addBranch('RB_1.0') 79 | 80 | commit() 81 | addTag('1.1.0-rc.1+abcde') 82 | addTag('also-not-a-version') 83 | 84 | addBranch('test') 85 | checkout('test') 86 | commit() 87 | addTag('2.0.0-rc.1') 88 | addBranch('REL_2.1') 89 | commit() 90 | commit() 91 | checkout('REL_2.1') 92 | commit('2.txt') 93 | addTag('2.1.0-rc.1') 94 | checkout('test') 95 | merge('REL_2.1') 96 | } 97 | 98 | def cleanupSpec() { 99 | assert !repoDir.exists() || repoDir.deleteDir() 100 | } 101 | 102 | @Unroll('when on #head, locator finds normal #normal with nearest #any') 103 | def 'locator returns correct value'() { 104 | given: 105 | grgit.checkout(branch: head) 106 | expect: 107 | def nearest = new NearestVersionLocator(new TagStrategy()).locate(grgit) 108 | nearest.any == Version.valueOf(any) 109 | nearest.normal == Version.valueOf(normal) 110 | nearest.distanceFromAny == anyDistance 111 | nearest.distanceFromNormal == normalDistance 112 | where: 113 | head | any | normal | anyDistance | normalDistance 114 | 'master' | '1.1.0-rc.1+abcde' | '1.0.0' | 0 | 1 115 | 'RB_0.1' | '0.1.2-beta.1' | '0.1.1+2010.01.01.12.00.00' | 3 | 7 116 | 'RB_1.0' | '1.0.0' | '1.0.0' | 0 | 0 117 | 'no-normal' | '0.0.1-beta.3' | '0.0.0' | 0 | 3 118 | 'unreachable' | '0.0.0' | '0.0.0' | 2 | 2 119 | 'test' | '2.1.0-rc.1' | '1.0.0' | 3 | 6 120 | } 121 | 122 | private void commit(String name = '1.txt') { 123 | byte[] bytes = new byte[128] 124 | random.nextBytes(bytes) 125 | new File(grgit.repository.rootDir, name) << bytes 126 | grgit.add(patterns: [name]) 127 | def commit = grgit.commit(message: 'do') 128 | println "Created commit: ${commit.abbreviatedId}" 129 | } 130 | 131 | private void addBranch(String name) { 132 | def currentHead = grgit.head() 133 | def currentBranch = grgit.branch.current 134 | def newBranch = grgit.branch.add(name: name) 135 | def atCommit = grgit.resolve.toCommit(newBranch.fullName) 136 | println "Added new branch ${name} at ${atCommit.abbreviatedId}" 137 | assert currentBranch == grgit.branch.current 138 | assert currentHead == atCommit 139 | } 140 | 141 | private void addTag(String name) { 142 | def currentHead = grgit.head() 143 | def newTag = grgit.tag.add(name: name) 144 | def atCommit = grgit.resolve.toCommit(newTag.fullName) 145 | println "Added new tag ${name} at ${atCommit.abbreviatedId}" 146 | assert currentHead == atCommit 147 | } 148 | 149 | private void checkout(String name) { 150 | def currentHead = grgit.head() 151 | grgit.checkout(branch: name) 152 | def atCommit = grgit.resolve.toCommit(name) 153 | def newHead = grgit.head() 154 | println "Checkout out ${name}, which is at ${atCommit.abbreviatedId}" 155 | assert atCommit == newHead 156 | assert name == grgit.branch.current.name 157 | } 158 | 159 | private void merge(String name) { 160 | def currentHead = grgit.head() 161 | grgit.merge(head: name) 162 | println "Merged ${name}, now at ${grgit.head().abbreviatedId}" 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/test/groovy/org/ajoberstar/gradle/git/release/semver/RebuildVersionStrategySpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.semver 17 | 18 | import org.ajoberstar.gradle.git.release.base.ReleaseVersion 19 | import org.ajoberstar.grgit.Commit 20 | import org.ajoberstar.grgit.Grgit 21 | import org.ajoberstar.grgit.Status 22 | import org.ajoberstar.grgit.Tag 23 | import org.ajoberstar.grgit.service.TagService 24 | 25 | import org.gradle.api.Project 26 | import org.gradle.testfixtures.ProjectBuilder 27 | import spock.lang.Specification 28 | 29 | class RebuildVersionStrategySpec extends Specification { 30 | RebuildVersionStrategy strategy = new RebuildVersionStrategy() 31 | Grgit grgit = GroovyMock() 32 | 33 | def getProject(Map properties) { 34 | Project p = ProjectBuilder.builder().withName("testproject").build() 35 | p.apply plugin: "org.ajoberstar.release-base" 36 | properties.each { k, v -> 37 | p.ext[k] = v 38 | } 39 | p 40 | } 41 | 42 | def 'selector returns false if repo is dirty'() { 43 | given: 44 | mockClean(false) 45 | Project project = getProject([:]) 46 | mockTagsAtHead('v1.0.0') 47 | expect: 48 | !strategy.selector(project, grgit) 49 | } 50 | 51 | def 'selector returns false if any release properties are set'() { 52 | given: 53 | mockClean(true) 54 | Project project = getProject('release.scope': 'value') 55 | mockTagsAtHead('v1.0.0') 56 | expect: 57 | !strategy.selector(project, grgit) 58 | } 59 | 60 | def 'selector returns false if no version tag at HEAD'() { 61 | given: 62 | mockClean(true) 63 | Project project = getProject([:]) 64 | mockTagsAtHead('non-version-tag') 65 | expect: 66 | !strategy.selector(project, grgit) 67 | } 68 | 69 | def 'selector returns true if rebuild is attempted'() { 70 | given: 71 | mockClean(true) 72 | Project project = getProject([:]) 73 | mockTagsAtHead('v0.1.1', 'v1.0.0', '0.19.1') 74 | expect: 75 | strategy.selector(project, grgit) 76 | } 77 | 78 | def 'infer returns HEAD version is inferred and previous with create tag false'() { 79 | given: 80 | mockClean(true) 81 | Project project = getProject([:]) 82 | mockTagsAtHead('v0.1.1', 'v1.0.0', '0.19.1') 83 | expect: 84 | strategy.infer(project, grgit) == new ReleaseVersion('1.0.0', '1.0.0', false) 85 | } 86 | 87 | private void mockTagsAtHead(String... tagNames) { 88 | Commit head = new Commit() 89 | grgit.head() >> head 90 | TagService tag = GroovyMock() 91 | grgit.tag >> tag 92 | tag.list() >> tagNames.collect { new Tag(commit: head, fullName: "refs/heads/${it}") } 93 | } 94 | 95 | private void mockClean(boolean clean) { 96 | Status status = GroovyMock() 97 | grgit.status() >> status 98 | status.clean >> clean 99 | } 100 | 101 | private void mockProperties(Map props) { 102 | project.properties.clear() 103 | project.properties.putAll(props) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/test/groovy/org/ajoberstar/gradle/git/release/semver/SemVerStrategySpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.semver 17 | 18 | import com.github.zafarkhaja.semver.Version 19 | 20 | import org.ajoberstar.gradle.git.release.base.ReleaseVersion 21 | import org.ajoberstar.grgit.Branch 22 | import org.ajoberstar.grgit.Grgit 23 | import org.ajoberstar.grgit.Status 24 | import org.ajoberstar.grgit.service.BranchService 25 | 26 | import org.gradle.api.GradleException 27 | import org.gradle.api.Project 28 | 29 | import spock.lang.Specification 30 | 31 | class SemVerStrategySpec extends Specification { 32 | Project project = GroovyMock() 33 | Grgit grgit = GroovyMock() 34 | 35 | def 'selector returns false if stage is not set to valid value'() { 36 | given: 37 | def strategy = new SemVerStrategy(stages: ['one', 'two'] as SortedSet) 38 | mockStage(stageProp) 39 | expect: 40 | !strategy.selector(project, grgit) 41 | where: 42 | stageProp << [null, 'test'] 43 | } 44 | 45 | 46 | def 'selector returns false if repo is dirty and not allowed to be'() { 47 | given: 48 | def strategy = new SemVerStrategy(stages: ['one'] as SortedSet, allowDirtyRepo: false) 49 | mockStage('one') 50 | mockRepoClean(false) 51 | expect: 52 | !strategy.selector(project, grgit) 53 | } 54 | 55 | def 'selector returns true if repo is dirty and allowed and other criteria met'() { 56 | given: 57 | def strategy = new SemVerStrategy(stages: ['one'] as SortedSet, allowDirtyRepo: true) 58 | mockStage('one') 59 | mockRepoClean(false) 60 | mockBranchService() 61 | expect: 62 | strategy.selector(project, grgit) 63 | } 64 | 65 | def 'selector returns true if all criteria met'() { 66 | given: 67 | def strategy = new SemVerStrategy(stages: ['one', 'and'] as SortedSet, allowDirtyRepo: false) 68 | mockStage('one') 69 | mockRepoClean(true) 70 | mockBranchService() 71 | expect: 72 | strategy.selector(project, grgit) 73 | } 74 | 75 | def 'default selector returns false if stage is defined but not set to valid value'() { 76 | given: 77 | def strategy = new SemVerStrategy(stages: ['one', 'two'] as SortedSet) 78 | mockStage('test') 79 | expect: 80 | !strategy.defaultSelector(project, grgit) 81 | } 82 | 83 | def 'default selector returns true if stage is not defined'() { 84 | given: 85 | def strategy = new SemVerStrategy(stages: ['one', 'two'] as SortedSet) 86 | mockStage(null) 87 | mockRepoClean(true) 88 | expect: 89 | strategy.defaultSelector(project, grgit) 90 | } 91 | 92 | def 'default selector returns false if repo is dirty and not allowed to be'() { 93 | given: 94 | def strategy = new SemVerStrategy(stages: ['one'] as SortedSet, allowDirtyRepo: false) 95 | mockStage(stageProp) 96 | mockRepoClean(false) 97 | expect: 98 | !strategy.defaultSelector(project, grgit) 99 | where: 100 | stageProp << [null, 'one'] 101 | } 102 | 103 | def 'default selector returns true if repo is dirty and allowed and other criteria met'() { 104 | given: 105 | def strategy = new SemVerStrategy(stages: ['one'] as SortedSet, allowDirtyRepo: true) 106 | mockStage('one') 107 | mockRepoClean(false) 108 | mockBranchService() 109 | expect: 110 | strategy.defaultSelector(project, grgit) 111 | } 112 | 113 | def 'default selector returns true if all criteria met'() { 114 | given: 115 | def strategy = new SemVerStrategy(stages: ['one', 'and'] as SortedSet, allowDirtyRepo: false) 116 | mockStage('one') 117 | mockRepoClean(true) 118 | mockBranchService() 119 | expect: 120 | strategy.defaultSelector(project, grgit) 121 | } 122 | 123 | def 'infer returns correct version'() { 124 | given: 125 | mockScope(scope) 126 | mockStage(stage) 127 | mockRepoClean(false) 128 | mockBranchService() 129 | def nearest = new NearestVersion( 130 | normal: Version.valueOf('1.2.2'), 131 | any: Version.valueOf(nearestAny)) 132 | def locator = mockLocator(nearest) 133 | def strategy = mockStrategy(scope, stage, nearest, createTag, enforcePrecedence) 134 | expect: 135 | strategy.doInfer(project, grgit, locator) == new ReleaseVersion('1.2.3-beta.1+abc123', '1.2.2', createTag) 136 | where: 137 | scope | stage | nearestAny | createTag | enforcePrecedence 138 | 'patch' | 'one' | '1.2.3' | true | false 139 | 'minor' | 'one' | '1.2.2' | true | true 140 | 'major' | 'one' | '1.2.2' | false | true 141 | 'patch' | null | '1.2.2' | false | true 142 | } 143 | 144 | def 'infer fails if stage is not listed in stages property'() { 145 | given: 146 | mockStage('other') 147 | def strategy = new SemVerStrategy(stages: ['one'] as SortedSet) 148 | when: 149 | strategy.doInfer(project, grgit, null) 150 | then: 151 | thrown(GradleException) 152 | } 153 | 154 | def 'infer fails if precedence enforced and violated'() { 155 | given: 156 | mockRepoClean(false) 157 | mockBranchService() 158 | def nearest = new NearestVersion(any: Version.valueOf('1.2.3')) 159 | def locator = mockLocator(nearest) 160 | def strategy = mockStrategy(null, 'and', nearest, false, true) 161 | when: 162 | strategy.doInfer(project, grgit, locator) 163 | then: 164 | thrown(GradleException) 165 | } 166 | 167 | private def mockScope(String scopeProp) { 168 | (0..1) * project.hasProperty('release.scope') >> (scopeProp as boolean) 169 | (0..1) * project.property('release.scope') >> scopeProp 170 | } 171 | 172 | private def mockStage(String stageProp) { 173 | (0..1) * project.hasProperty('release.stage') >> (stageProp as boolean) 174 | (0..1) * project.property('release.stage') >> stageProp 175 | } 176 | 177 | private def mockRepoClean(boolean isClean) { 178 | Status status = GroovyMock() 179 | (0..2) * status.clean >> isClean 180 | (0..2) * grgit.status() >> status 181 | 0 * status._ 182 | } 183 | 184 | private def mockBranchService() { 185 | BranchService branchService = GroovyMock() 186 | (0..1) * branchService.current >> new Branch(fullName: 'refs/heads/master') 187 | (0..2) * grgit.getBranch() >> branchService 188 | 0 * branchService._ 189 | } 190 | 191 | private def mockLocator(NearestVersion nearest) { 192 | NearestVersionLocator locator = Mock() 193 | locator.locate(grgit) >> nearest 194 | return locator 195 | } 196 | 197 | private def mockStrategy(String scope, String stage, NearestVersion nearest, boolean createTag, boolean enforcePrecedence) { 198 | PartialSemVerStrategy normal = Mock() 199 | PartialSemVerStrategy preRelease = Mock() 200 | PartialSemVerStrategy buildMetadata = Mock() 201 | SemVerStrategyState initial = new SemVerStrategyState([ 202 | scopeFromProp: scope?.toUpperCase(), 203 | stageFromProp: stage ?: 'and', 204 | currentHead: null, 205 | currentBranch: new Branch(fullName: 'refs/heads/master'), 206 | repoDirty: true, 207 | nearestVersion: nearest]) 208 | SemVerStrategyState afterNormal = initial.copyWith(inferredNormal: '1.2.3') 209 | SemVerStrategyState afterPreRelease = afterNormal.copyWith(inferredPreRelease: 'beta.1') 210 | SemVerStrategyState afterBuildMetadata = afterPreRelease.copyWith(inferredBuildMetadata: 'abc123') 211 | 212 | 1 * normal.infer(initial) >> afterNormal 213 | 1 * preRelease.infer(afterNormal) >> afterPreRelease 214 | 1 * buildMetadata.infer(afterPreRelease) >> afterBuildMetadata 215 | 0 * normal._ 216 | 0 * preRelease._ 217 | 0 * buildMetadata._ 218 | 219 | return new SemVerStrategy( 220 | stages: ['one', 'and'] as SortedSet, 221 | normalStrategy: normal, 222 | preReleaseStrategy: preRelease, 223 | buildMetadataStrategy: buildMetadata, 224 | createTag: createTag, 225 | enforcePrecedence: enforcePrecedence 226 | ) 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/test/groovy/org/ajoberstar/gradle/git/release/semver/StrategyUtilSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.ajoberstar.gradle.git.release.semver 17 | 18 | import spock.lang.Specification 19 | 20 | class StrategyUtilSpec extends Specification { 21 | SemVerStrategyState initialState = new SemVerStrategyState([:]) 22 | 23 | def 'closure backed uses behavior passed in'() { 24 | expect: 25 | stringReplace('a').infer(initialState) == new SemVerStrategyState(inferredPreRelease: 'a') 26 | } 27 | 28 | def 'choose one returns the first that changes state'() { 29 | given: 30 | def chain = StrategyUtil.one(nothing(), stringReplace('a'), stringReplace('b'), nothing()) 31 | expect: 32 | chain.infer(initialState) == new SemVerStrategyState(inferredPreRelease: 'a') 33 | } 34 | 35 | def 'apply all uses all strategies in order'() { 36 | given: 37 | def chain = StrategyUtil.all(stringAppend('a'), stringAppend('b'), stringAppend('c')) 38 | expect: 39 | chain.infer(initialState) == new SemVerStrategyState(inferredPreRelease: 'a.b.c') 40 | } 41 | 42 | private def nothing() { 43 | return StrategyUtil.closure { state -> state } 44 | } 45 | 46 | private def stringReplace(String str) { 47 | return StrategyUtil.closure { state -> state.copyWith(inferredPreRelease: str) } 48 | } 49 | 50 | private def stringAppend(String str) { 51 | return StrategyUtil.closure { state -> 52 | def preRelease = state.inferredPreRelease ? "${state.inferredPreRelease}.${str}" : str 53 | return state.copyWith(inferredPreRelease: preRelease) 54 | } 55 | } 56 | } 57 | --------------------------------------------------------------------------------