├── .travis.yml ├── README.md ├── build.gradle ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src ├── main ├── groovy │ └── no │ │ └── entitas │ │ └── gradle │ │ ├── ReleasePlugin.groovy │ │ ├── ReleasePluginExtension.groovy │ │ ├── Version.groovy │ │ ├── git │ │ ├── GitReleasePlugin.groovy │ │ └── GitVersion.groovy │ │ └── svn │ │ ├── LocalChangesChecker.groovy │ │ ├── NullSVNDebugLog.java │ │ ├── SvnReleasePlugin.groovy │ │ ├── SvnVersion.groovy │ │ └── UpToDateChecker.groovy └── resources │ └── META-INF │ └── gradle-plugins │ ├── gitrelease.properties │ └── svnrelease.properties ├── sample ├── build.gradle ├── main │ └── src │ │ └── main │ │ └── java │ │ └── Main.java ├── settings.gradle └── util │ └── src │ └── main │ └── java │ └── Util.java └── test └── groovy └── no └── entitas └── gradle └── git └── GitVersionTest.groovy /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gradle release plugin (Git and Subversion) [![Build Status](https://secure.travis-ci.org/stianh/gradle-release-plugin.png?branch=develop)](http://travis-ci.org/stianh/gradle-release-plugin) 2 | 3 | This is a Gradle plugin that makes it very simple to automate release management when using 4 | git or Subversion as vcs. The plugin is responsible for knowing the version to build at all 5 | times. You should not use this plugin if you want/need to be in control of the version 6 | name/number. 7 | 8 | In the case of a normal gradle build, the plugin generates a version name based on the 9 | current branch name: ${branchName}-SNAPSHOT. 10 | 11 | If you run the task releasePrepare, the plugin will query git/svn for the latest release tag 12 | and add one to the number. The artifacts will have a version name like master-REL-1 (if this 13 | is the first time you run :releasePrepare). A release tag with the version name will be 14 | created in git/svn. (The tag will NOT be pushed in this task when using git.) 15 | 16 | If you want the artifacts to be uploaded and the releaseTag to be pushed, run the 17 | releasePerform task. This task depends on releasePrepare so it will run first. 18 | 19 | **Notice:** The build will fail if you try to run releasePrepare again, with an error message 20 | telling you that there is no changes since the last release tag. If you want to rebuild a 21 | release, just run a normal gradle build and the plugin will figure out that the current HEAD 22 | is a release tag and use the release version. The same applies if you checkout a release tag 23 | and run build. 24 | 25 | ## Usage 26 | 27 | Add the following to your build file to setup where the plugin should be downloaded from: 28 | 29 | ```groovy 30 | apply plugin: 'gitrelease' // or apply plugin: 'svnrelease' 31 | 32 | buildscript { 33 | repositories { 34 | mavenCentral() 35 | } 36 | 37 | dependencies { 38 | classpath 'no.entitas.gradle:gradle-release-plugin:1.16' 39 | } 40 | } 41 | ``` 42 | 43 | **Notice:** This *must* be in the root build file in a multi-module build, that is, 44 | the release plugin can only be applied at the top level. 45 | 46 | To setup where your artifacts should be deployed, use a regular `uploadArchives` section. 47 | This is an example of deploying to a Maven repository: 48 | 49 | ```groovy 50 | uploadArchives.repositories.mavenDeployer { 51 | uniqueVersion = false 52 | 53 | repository(url: '...release distribution url...') { 54 | // username/password resolved from gradle.properties 55 | authentication(userName: project.username, password: project.password) 56 | } 57 | 58 | snapshotRepository(url: '...snapshot distribution url...') { 59 | // username/password resolved from gradle.properties 60 | authentication(userName: project.username, password: project.password) 61 | } 62 | } 63 | ``` 64 | 65 | In a multi-module build this will typically be setup for each subproject that needs to be 66 | deployed. 67 | 68 | ## Configuration 69 | 70 | The closure below shows the available configuration options and their default values: 71 | 72 | ```groovy 73 | release { 74 | failOnSnapshotDependencies = false 75 | versionStrategy = { currentVersion -> 76 | if (System.properties['release.version']) { 77 | System.properties['release.version'] 78 | } else { 79 | new BigDecimal(currentVersion).add(BigDecimal.ONE).toPlainString() 80 | } 81 | } 82 | startVersion = { currentBranch -> "1" } 83 | } 84 | ``` 85 | 86 | **`failOnSnapshotDependencies`** when set to true the build will fail if it has any snapshot dependencies. 87 | 88 | **`versionStrategy`** a closure for calculating the next version number, given the current (as a String). 89 | The default implementation is to add 1, or if the system property *release.version* is set, use its value. 90 | 91 | **`startVersion`** which version to start counting from. 92 | 93 | 94 | ## Tasks 95 | 96 | **releasePrepare** 97 | * Checks that there are no local modifications (git/svn status) 98 | * Checks that your current HEAD is not a release tag 99 | * The projects are built with version resolved from the latest git release tag + 1 100 | * Creates a git tag for your current head named ${branchName}-REL-${version} 101 | 102 | **releasePerform** 103 | * This task depends on the :releasePrepare task 104 | * Depends on uploadArtifacts and pushes tags if using git 105 | 106 | 107 | ## Known issues and limitations 108 | 109 | * Only tested on Java projects 110 | * The releasePerform task has only been tested with Nexus and http upload -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 | apply plugin: 'groovy' 17 | apply plugin: 'maven' 18 | apply plugin: 'signing' 19 | 20 | version = '1.16' 21 | group = 'no.entitas.gradle' 22 | 23 | repositories { 24 | mavenCentral() 25 | } 26 | 27 | dependencies { 28 | compile gradleApi() 29 | compile 'org.tmatesoft.svnkit:svnkit:1.7.5' 30 | compile 'org.eclipse.jgit:org.eclipse.jgit:2.1.0.201209190230-r' 31 | compile 'com.jcraft:jsch:0.1.44-1' 32 | 33 | testCompile 'org.spockframework:spock-core:0.6-groovy-1.8' 34 | testCompile 'cglib:cglib-nodep:2.2' 35 | testCompile 'org.objenesis:objenesis:1.2' 36 | testCompile 'junit:junit-dep:4.9' 37 | 38 | groovy 'org.codehaus.groovy:groovy-all:1.8.6' 39 | } 40 | 41 | // TODO need to create svn/git repos to run the sample on 42 | task integrationTest(type: GradleBuild, dependsOn: build) { 43 | dir = 'src/sample' 44 | tasks = ['clean', 'build'] 45 | } 46 | 47 | task sourcesJar(type: Jar, dependsOn: classes) { 48 | classifier = 'sources' 49 | from sourceSets.main.allSource 50 | } 51 | 52 | task javadocJar(type: Jar, dependsOn: javadoc) { 53 | classifier = 'javadoc' 54 | from javadoc.destinationDir 55 | } 56 | 57 | artifacts { 58 | archives sourcesJar 59 | archives javadocJar 60 | } 61 | 62 | signing { 63 | required = { gradle.taskGraph.hasTask("uploadArchives") && !version.endsWith("SNAPSHOT") } 64 | sign configurations.archives 65 | } 66 | 67 | ext.defaultProject = { 68 | name 'Gradle Release plugin' 69 | // packaging 'jar' (Packaging is set later through direct modification of the xml) 70 | description 'The Gradle Release plugin makes releasing Gradle projects easy.' 71 | url 'http://github.com/stianh/gradle-release-plugin' 72 | inceptionYear '2011' 73 | 74 | scm { 75 | developerConnection 'git@github.com:stianh/gradle-release-plugin.git' 76 | connection 'git://github.com/stianh/gradle-release-plugin.git' 77 | url 'https://github.com/stianh/gradle-release-plugin' 78 | } 79 | 80 | licenses { 81 | license { 82 | name 'The Apache Software License, Version 2.0' 83 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 84 | distribution 'repo' 85 | } 86 | } 87 | 88 | developers { 89 | developer { 90 | id 'stianh' 91 | name 'Stian Hegglund' 92 | email 'stian.hegglund@gmail.com' 93 | roles { 94 | role 'Developer' 95 | } 96 | timezone '+1' 97 | } 98 | developer { 99 | id 'mkrabset' 100 | name 'Marius Krabset' 101 | email 'marius.krabset@gmail.com' 102 | roles { 103 | role 'Developer' 104 | } 105 | timezone '+1' 106 | } 107 | developer { 108 | id 'stigkj' 109 | name 'Stig Kleppe-Jørgensen' 110 | email 'from.gradle-release@nisgits.net' 111 | roles { 112 | role 'Developer' 113 | } 114 | timezone '+1' 115 | } 116 | } 117 | } 118 | 119 | uploadArchives.repositories.mavenDeployer { 120 | uniqueVersion = false 121 | 122 | if (!project.hasProperty('sonatypeUsername')) sonatypeUsername = '' 123 | if (!project.hasProperty('sonatypePassword')) sonatypePassword = '' 124 | 125 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { 126 | authentication(userName: sonatypeUsername, password: sonatypePassword) 127 | } 128 | 129 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { 130 | authentication(userName: sonatypeUsername, password: sonatypePassword) 131 | } 132 | } 133 | 134 | ext.fixedProject = { 135 | project defaultProject 136 | withXml { 137 | def project = it.asNode() 138 | project.version[0] + { 139 | packaging 'jar' 140 | } 141 | } 142 | } 143 | 144 | uploadArchives.repositories.mavenDeployer { 145 | pom fixedProject 146 | 147 | beforeDeployment { 148 | signing.signPom(it) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Nov 01 13:24:38 CET 2012 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.0-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/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 businessSystem 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/main/groovy/no/entitas/gradle/ReleasePlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 no.entitas.gradle 17 | 18 | import org.gradle.api.Plugin 19 | import org.gradle.api.Project 20 | import org.gradle.api.Task 21 | import org.gradle.api.artifacts.Configuration 22 | import org.gradle.api.artifacts.Dependency 23 | 24 | abstract class ReleasePlugin implements Plugin { 25 | def TASK_RELEASE_PREPARE = 'releasePrepare' 26 | def TASK_RELEASE_PERFORM = 'releasePerform' 27 | def logger 28 | 29 | def void apply(Project project) { 30 | def version = createVersion(project) 31 | project.version = version 32 | project.extensions.release = new ReleasePluginExtension() 33 | logger = project.logger 34 | 35 | Task releasePrepareTask = project.task(TASK_RELEASE_PREPARE) << { 36 | if (project.release.failOnSnapshotDependencies) { 37 | project.allprojects.each { currentProject -> 38 | currentProject.configurations.each { configuration -> 39 | logger.info("Checking for snapshot dependencies in $currentProject.path -> $configuration.name") 40 | ensureNoSnapshotDependencies(configuration) 41 | } 42 | } 43 | } 44 | 45 | version.releasePrepare() 46 | } 47 | 48 | Task performReleaseTask = project.task(TASK_RELEASE_PERFORM) << { 49 | version.releasePerform() 50 | } 51 | 52 | if (project.subprojects.isEmpty()) { 53 | releasePrepareTask.dependsOn(project.tasks.build) 54 | performReleaseTask.dependsOn([releasePrepareTask, project.tasks.uploadArchives]) 55 | } else { 56 | /* 57 | TODO: The subprojects closure configuration is not applied at the time when apply is called for this plugin. 58 | The subprojects needs the java plugin at this time to resolve clean, build and the uploadArtifacts tasks. 59 | Investigate if this somehow can be done lazy. 60 | */ 61 | project.subprojects*.apply plugin: 'java' 62 | 63 | Task cleanAllTask = project.task('cleanAll') << {} 64 | cleanAllTask.dependsOn(project.subprojects*.clean) 65 | 66 | Task buildAll = project.task('buildAll') << {} 67 | buildAll.dependsOn([cleanAllTask, project.subprojects*.build]) 68 | 69 | releasePrepareTask.dependsOn(buildAll) 70 | performReleaseTask.dependsOn([releasePrepareTask, project.subprojects*.uploadArchives]) 71 | } 72 | } 73 | 74 | def ensureNoSnapshotDependencies(Configuration configuration) { 75 | def snapshotDependencies = [] as Set 76 | 77 | configuration.allDependencies.each { Dependency dependency -> 78 | logger.debug(" Checking ${dependency.name}") 79 | 80 | if (dependency.version?.contains('SNAPSHOT')) { 81 | snapshotDependencies.add("${dependency.group}:${dependency.name}:${dependency.version}") 82 | } 83 | } 84 | 85 | if (!snapshotDependencies.isEmpty()) { 86 | throw new IllegalStateException("Project contains SNAPSHOT dependencies: ${snapshotDependencies}") 87 | } 88 | } 89 | 90 | abstract def Version createVersion(Project project) 91 | } -------------------------------------------------------------------------------- /src/main/groovy/no/entitas/gradle/ReleasePluginExtension.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 no.entitas.gradle 17 | 18 | /** 19 | * Extension object for the release plugin with options that the user can set 20 | * 21 | * @author Stig Kleppe-Jorgensen, 2012.01.12 22 | */ 23 | class ReleasePluginExtension { 24 | def boolean failOnSnapshotDependencies = true 25 | def versionStrategy = { currentVersion -> 26 | if (System.properties['release.version']) { 27 | System.properties['release.version'] 28 | } else { 29 | new BigDecimal(currentVersion).add(BigDecimal.ONE).toPlainString() 30 | } 31 | } 32 | def startVersion = { currentBranch -> "1" } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/groovy/no/entitas/gradle/Version.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 no.entitas.gradle 17 | 18 | interface Version { 19 | def releasePrepare(); 20 | def releasePerform(); 21 | } -------------------------------------------------------------------------------- /src/main/groovy/no/entitas/gradle/git/GitReleasePlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 no.entitas.gradle.git 17 | 18 | import no.entitas.gradle.ReleasePlugin 19 | import no.entitas.gradle.Version 20 | import org.eclipse.jgit.lib.RepositoryBuilder 21 | import org.gradle.api.Project 22 | 23 | class GitReleasePlugin extends ReleasePlugin { 24 | def Version createVersion(Project project) { 25 | def repository = new RepositoryBuilder().setMustExist(true). 26 | findGitDir(new File('.').absoluteFile).build(); 27 | 28 | return new GitVersion(project, repository); 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/groovy/no/entitas/gradle/git/GitVersion.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 no.entitas.gradle.git 17 | 18 | import java.util.regex.Pattern 19 | import no.entitas.gradle.Version 20 | import org.eclipse.jgit.api.Git 21 | import org.eclipse.jgit.api.Status 22 | import org.eclipse.jgit.transport.RemoteConfig 23 | import org.eclipse.jgit.lib.BranchTrackingStatus 24 | import org.eclipse.jgit.lib.Constants 25 | import org.eclipse.jgit.lib.IndexDiff 26 | import org.eclipse.jgit.lib.Repository 27 | import org.eclipse.jgit.revwalk.RevWalk 28 | import org.eclipse.jgit.treewalk.FileTreeIterator 29 | import org.gradle.api.GradleException 30 | import org.gradle.api.Project 31 | import org.gradle.api.resources.MissingResourceException 32 | 33 | class GitVersion implements Version { 34 | Pattern releaseTagPattern = ~/^\S+-REL-\d+$/ 35 | 36 | Project project 37 | Repository repository 38 | Map tagsKeyedOnCommitsObjectId 39 | String versionNumber 40 | Status status 41 | 42 | def GitVersion(Project project, Repository repository) { 43 | this.project = project 44 | this.repository = repository 45 | this.tagsKeyedOnCommitsObjectId = tagsKeyedOnCommitsObjectId(repository) 46 | this.status = workDirStatus(repository) 47 | 48 | project.gradle.taskGraph.whenReady { graph -> 49 | setup(graph) 50 | } 51 | } 52 | 53 | def setup(graph) { 54 | project.logger.info("Setting version number...") 55 | 56 | if (graph.hasTask(':releasePrepare')) { 57 | releasePreConditions() 58 | versionNumber = getNextTagName() 59 | project.logger.info(" New version number for release build is ${versionNumber}") 60 | } else if (isOnReleaseTag()) { 61 | if (!status.clean) { 62 | versionNumber = getCurrentBranchName() + '-SNAPSHOT' 63 | project.logger.info(" Version number for build on release tag with local modification is ${versionNumber}") 64 | } else { 65 | versionNumber = getCurrentVersion() 66 | project.logger.info(" Version number for build on release tag is ${versionNumber}") 67 | } 68 | } else { 69 | versionNumber = getCurrentBranchName() + '-SNAPSHOT' 70 | project.logger.info(" Version number for regular build is ${versionNumber}") 71 | } 72 | } 73 | 74 | // TODO Ensure that we are on a branch 75 | def releasePreConditions() { 76 | if (!status.clean) { 77 | throw new GradleException('Changes found in the source tree:\n' + buildStatusText()) 78 | } 79 | 80 | if (isOnReleaseTag()) { 81 | throw new GradleException('No changes since last tag') 82 | } 83 | 84 | if (branchIsAheadOfRemote()) { 85 | throw new GradleException('Project contains unpushed commits'); 86 | } 87 | } 88 | 89 | boolean branchIsAheadOfRemote() { 90 | def status = BranchTrackingStatus.of(repository, repository.branch) 91 | 92 | if (status.hasProperty("aheadCount")) { 93 | return status.aheadCount != 0 94 | } 95 | 96 | return false 97 | } 98 | 99 | String toString() { 100 | versionNumber 101 | } 102 | 103 | def releasePrepare() { 104 | tag(versionNumber, "Release Tag: ${versionNumber}") 105 | } 106 | 107 | def releasePerform() { 108 | pushTags() 109 | } 110 | 111 | def workDirStatus(Repository repository) { 112 | def workingTreeIterator = new FileTreeIterator(repository) 113 | IndexDiff diff = new IndexDiff(repository, Constants.HEAD, workingTreeIterator) 114 | diff.diff() 115 | 116 | new Status(diff); 117 | } 118 | 119 | def buildStatusText() { 120 | """${stringify(status.changed, 'changed')} ${stringify(status.added, 'added')} ${stringify(status.conflicting, 'conflicting')} ${stringify(status.missing, 'missing')} ${stringify(status.modified, 'modified')} ${stringify(status.removed, 'removed')} ${stringify(status.untracked, 'untracked')}""" 121 | } 122 | 123 | def stringify(Set fileNames, String type) { 124 | if (fileNames) { 125 | "\nFiles ${type}:\n " + 126 | fileNames.join('\n ') 127 | } else { 128 | '' 129 | } 130 | } 131 | 132 | def isOnReleaseTag() { 133 | tagNamesOnCurrentRevision().any { String tagName -> 134 | tagName.matches(releaseTagPattern) 135 | } 136 | } 137 | 138 | def getCurrentVersion() { 139 | // TODO what if none is found? is it even possible? 140 | tagNamesOnCurrentRevision().find() { String tagName -> 141 | tagName.matches(releaseTagPattern) 142 | } 143 | } 144 | 145 | def tagNamesOnCurrentRevision() { 146 | def head = repository.getRef(Constants.HEAD) 147 | 148 | tagsKeyedOnCommitsObjectId.get(head.objectId) 149 | } 150 | 151 | def tagsKeyedOnCommitsObjectId(Repository repository) { 152 | def tagsKeyedOnCommitsObjectId = [:] 153 | 154 | repository.tags.each { tagEntry -> 155 | // TODO check for objectId == null? Happens if tag is not annotated 156 | def commitsObjectId = repository.peel(tagEntry.value).peeledObjectId 157 | Set set = tagsKeyedOnCommitsObjectId.get(commitsObjectId) 158 | 159 | if (set == null) { 160 | set = [] as Set 161 | tagsKeyedOnCommitsObjectId.put(commitsObjectId, set) 162 | } 163 | 164 | set.add(tagEntry.key) 165 | } 166 | 167 | tagsKeyedOnCommitsObjectId 168 | } 169 | 170 | def getCurrentBranchName() { 171 | def fullBranchName = repository.fullBranch 172 | 173 | if (!fullBranchName) { 174 | throw new MissingResourceException('Could not find the current branch name'); 175 | } else if (!fullBranchName.startsWith('refs/heads/')) { 176 | throw new MissingResourceException('Checkout the branch to release from'); 177 | } 178 | 179 | def branchName = Repository.shortenRefName(fullBranchName) 180 | normalizeBranchName(branchName) 181 | } 182 | 183 | def normalizeBranchName(String branchName) { 184 | branchName.replaceAll('[^\\w\\.\\-\\_]', '_'); 185 | } 186 | 187 | def getNextTagName() { 188 | def currentBranch = getCurrentBranchName() 189 | def latestReleaseTag = getLatestReleaseTag(currentBranch) 190 | 191 | if (latestReleaseTag) { 192 | nextReleaseTag(latestReleaseTag) 193 | } else { 194 | def startVersion = project.extensions.getByName('release').startVersion.call(currentBranch) 195 | formatTagName(currentBranch, startVersion) 196 | } 197 | } 198 | 199 | def nextReleaseTag(String previousReleaseTag) { 200 | def tagNameParts = previousReleaseTag.split('-').toList() 201 | def currentVersion = tagNameParts[-1] 202 | def nextVersion = project.extensions.getByName('release').versionStrategy.call(currentVersion) 203 | 204 | tagNameParts.pop() 205 | tagNameParts.pop() 206 | def branchName = tagNameParts.join('-') 207 | 208 | formatTagName(branchName, nextVersion) 209 | } 210 | 211 | def formatTagName(branchName, version) { 212 | "$branchName-REL-$version" 213 | } 214 | 215 | /** 216 | * 1. Find all tags that matches the release tag pattern 217 | * 2. Create a RevTag instance for each of these tags 218 | * 3. Sort the RevTags on creation date, newest last 219 | * 4. Retrieve the tag name of the newest 220 | */ 221 | def getLatestReleaseTag(String currentBranch) { 222 | def revWalk = new RevWalk(repository) 223 | 224 | try { 225 | def tags = repository.tags.findAll() { tagEntry -> 226 | tagEntry.value.name =~ /${currentBranch}-REL-*/ 227 | }.collect { releaseTag -> 228 | revWalk.parseTag(releaseTag.value.objectId) 229 | }.sort { revTag1, revTag2 -> 230 | // Reverse sort on tagging time so latest tag is first in list 231 | revTag2.taggerIdent.when <=> revTag1.taggerIdent.when 232 | } 233 | 234 | if (tags) { 235 | tags.head().tagName 236 | } else { 237 | null 238 | } 239 | } finally { 240 | revWalk.release() 241 | } 242 | } 243 | 244 | def tag(String tag, String message) { 245 | project.logger.info("tagging with $tag") 246 | // TODO log result? 247 | Git.wrap(repository).tag().setName(tag).setMessage(message).call() 248 | } 249 | 250 | def pushTags() { 251 | // TODO log result? 252 | List remoteConfigs = RemoteConfig.getAllRemoteConfigs(repository.config) 253 | 254 | if (remoteConfigs) { 255 | project.logger.info("pushing tags") 256 | Git.wrap(repository).push().setPushTags().call() 257 | } 258 | } 259 | } 260 | 261 | -------------------------------------------------------------------------------- /src/main/groovy/no/entitas/gradle/svn/LocalChangesChecker.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 no.entitas.gradle.svn 17 | 18 | import org.gradle.api.Project 19 | import org.tmatesoft.svn.core.SVNDepth 20 | import org.tmatesoft.svn.core.SVNException 21 | import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory 22 | import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl 23 | import org.tmatesoft.svn.core.wc.ISVNStatusHandler 24 | import org.tmatesoft.svn.core.wc.SVNClientManager 25 | import org.tmatesoft.svn.core.wc.SVNRevision 26 | import org.tmatesoft.svn.core.wc.SVNStatus 27 | import org.tmatesoft.svn.core.wc.SVNStatusType 28 | 29 | class LocalChangesChecker implements ISVNStatusHandler { 30 | boolean localModifications = false 31 | Project project 32 | 33 | LocalChangesChecker(Project project) { 34 | this.project = project 35 | } 36 | 37 | public boolean containsLocalModifications(SVNClientManager svnClientManager, File path,SVNRevision headRev) { 38 | SVNRepositoryFactoryImpl.setup(); 39 | FSRepositoryFactory.setup(); 40 | def statusClient=svnClientManager.getStatusClient() 41 | statusClient.doStatus(path, headRev, SVNDepth.INFINITY, true, true, true, false, this,null) 42 | return localModifications 43 | } 44 | 45 | public void handleStatus(SVNStatus status) throws SVNException { 46 | SVNStatusType statusType = status.getContentsStatus(); 47 | 48 | if (statusType != SVNStatusType.STATUS_NONE && 49 | statusType != SVNStatusType.STATUS_NORMAL && 50 | statusType != SVNStatusType.STATUS_IGNORED) { 51 | project.logger.debug("Local modifications: $status.file\t$statusType") 52 | localModifications = true; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/groovy/no/entitas/gradle/svn/NullSVNDebugLog.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 no.entitas.gradle.svn; 17 | 18 | import java.util.logging.Level; 19 | 20 | import org.gradle.api.Project; 21 | import org.tmatesoft.svn.core.internal.util.DefaultSVNDebugLogger; 22 | import org.tmatesoft.svn.util.SVNLogType; 23 | 24 | public class NullSVNDebugLog extends DefaultSVNDebugLogger { 25 | Project project; 26 | 27 | public NullSVNDebugLog(Project project) { 28 | this.project = project; 29 | } 30 | 31 | public void logError(SVNLogType logType, String message) { 32 | project.getLogger().error("ERROR: "+message); 33 | } 34 | 35 | public void logError(SVNLogType logType, Throwable th){ 36 | th.printStackTrace(); 37 | }; 38 | 39 | public void logSevere(SVNLogType logType, String message){ 40 | project.getLogger().error("SEVERE: "+message); 41 | }; 42 | 43 | public void logSevere(SVNLogType logType, Throwable th){ 44 | th.printStackTrace(); 45 | }; 46 | 47 | public void logFine(SVNLogType logType, Throwable th){}; 48 | 49 | public void logFine(SVNLogType logType, String message){}; 50 | 51 | public void logFiner(SVNLogType logType, Throwable th){}; 52 | 53 | public void logFiner(SVNLogType logType, String message){}; 54 | 55 | public void logFinest(SVNLogType logType, Throwable th){}; 56 | 57 | public void logFinest(SVNLogType logType, String message){}; 58 | 59 | public void log(SVNLogType logType, Throwable th, Level logLevel){}; 60 | 61 | public void log(SVNLogType logType, String message, Level logLevel){}; 62 | 63 | public void log(SVNLogType logType, String message, byte[] data){}; 64 | } 65 | -------------------------------------------------------------------------------- /src/main/groovy/no/entitas/gradle/svn/SvnReleasePlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 no.entitas.gradle.svn 17 | 18 | import no.entitas.gradle.ReleasePlugin 19 | import no.entitas.gradle.Version 20 | import org.gradle.api.Project 21 | 22 | class SvnReleasePlugin extends ReleasePlugin { 23 | def Version createVersion(Project project) { 24 | return new SvnVersion(project); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/groovy/no/entitas/gradle/svn/SvnVersion.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 no.entitas.gradle.svn 17 | 18 | import no.entitas.gradle.Version 19 | import org.gradle.api.GradleException 20 | import org.gradle.api.Project 21 | import org.tmatesoft.svn.core.SVNDirEntry 22 | import org.tmatesoft.svn.core.SVNURL 23 | import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; 24 | import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory 25 | import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory 26 | import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl 27 | import org.tmatesoft.svn.core.internal.util.SVNPathUtil 28 | import org.tmatesoft.svn.core.io.SVNRepository 29 | import org.tmatesoft.svn.core.io.SVNRepositoryFactory 30 | import org.tmatesoft.svn.core.wc.SVNClientManager 31 | import org.tmatesoft.svn.core.wc.SVNCopySource 32 | import org.tmatesoft.svn.core.wc.SVNInfo 33 | import org.tmatesoft.svn.core.wc.SVNRevision 34 | import org.tmatesoft.svn.core.wc.SVNStatus 35 | import org.tmatesoft.svn.core.wc.SVNWCUtil 36 | import org.tmatesoft.svn.util.SVNDebugLog 37 | 38 | class SvnVersion implements Version { 39 | private final def releaseTagPattern = ~/^(\S+)-REL-(\d+)$/ 40 | private final Project project 41 | private final SVNStatus svnStatus; 42 | private final RepoInfo repoInfo 43 | private final String tagName 44 | private Boolean release; 45 | private String versionNumber; 46 | 47 | SvnVersion(Project project) { 48 | this.project=project; 49 | SVNRepositoryFactoryImpl.setup(); 50 | FSRepositoryFactory.setup(); 51 | DAVRepositoryFactory.setup(); 52 | SVNDebugLog.setDefaultLog(new NullSVNDebugLog(project)); 53 | def svnClientManager=SVNClientManager.newInstance(); 54 | this.svnStatus=svnClientManager.getStatusClient().doStatus(project.rootDir,false) 55 | this.repoInfo=getRepoInfo(svnClientManager,svnStatus) 56 | //println("RepoInfo: "+repoInfo) 57 | 58 | def svnRepo=SVNRepositoryFactory.create(repoInfo.rootURL) 59 | 60 | //set an auth manager which will provide user credentials 61 | ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(); 62 | svnRepo.setAuthenticationManager(authManager); 63 | 64 | def latestTag=getLatestTag(svnRepo, repoInfo.branchName) 65 | def nextVersionNumber=getNextVersionNumber(latestTag) 66 | this.tagName=repoInfo.branchName+"-REL-"+nextVersionNumber; 67 | svnClientManager.dispose() 68 | 69 | project.gradle.taskGraph.whenReady {graph -> 70 | if (graph.hasTask(':releasePrepare')) { 71 | checkUpToDateAndNoLocalModifications(svnClientManager,repoInfo) 72 | this.release=true; 73 | this.versionNumber = tagName 74 | } else { 75 | this.release=false; 76 | this.versionNumber = repoInfo.branchName + '-SNAPSHOT' 77 | } 78 | } 79 | } 80 | 81 | def String toString() { 82 | return versionNumber; 83 | } 84 | 85 | def releasePrepare() { 86 | project.logger.debug("RepoInfo: $repoInfo") 87 | project.logger.info("Tag to create: $tagName") 88 | } 89 | 90 | def releasePerform() { 91 | def svnClientManager=SVNClientManager.newInstance(); 92 | checkUpToDateAndNoLocalModifications(svnClientManager,repoInfo) 93 | createTag(svnClientManager, svnStatus, repoInfo, tagName); 94 | svnClientManager.dispose() 95 | } 96 | 97 | private void checkUpToDateAndNoLocalModifications(SVNClientManager svnClientManager, RepoInfo repoInfo) { 98 | def containsLocalModifications = new LocalChangesChecker(project). 99 | containsLocalModifications(svnClientManager, project.rootDir, repoInfo.headRev) 100 | 101 | if (containsLocalModifications) { 102 | throw new IllegalStateException("Workspace contains local modifications."); 103 | } 104 | 105 | def containsRemoteModifications = new UpToDateChecker(project). 106 | containsRemoteModifications(svnClientManager, project.rootDir, repoInfo.headRev) 107 | 108 | if (containsRemoteModifications) { 109 | throw new IllegalStateException("Workspace is not up-to-date.") 110 | } 111 | } 112 | 113 | private RepoInfo getRepoInfo(SVNClientManager svnClientManager, SVNStatus svnStatus) { 114 | def url=svnStatus.URL; 115 | def headRevision=getHeadRevision(url,svnClientManager); 116 | def pathTail=SVNPathUtil.tail(url.getPath()) 117 | if ("trunk".equals(pathTail)) { 118 | def rootURL=url.removePathTail(); 119 | def tagsURL=rootURL.appendPath("tags",false) 120 | return new RepoInfo(rootURL,"trunk",false,tagsURL,headRevision); 121 | } else if ("branches".equals(SVNPathUtil.tail(url.removePathTail().getPath()))) { 122 | def branchName=SVNPathUtil.tail(url.getPath()) 123 | def rootURL=url.removePathTail().removePathTail(); 124 | def tagsURL=rootURL.appendPath("tags",false) 125 | return new RepoInfo(rootURL,branchName,true,tagsURL,headRevision); 126 | } else { 127 | throw new RuntimeException("Illegal url: "+url.getPath()+". Must end with /trunk or /branches/.") 128 | } 129 | } 130 | 131 | private SVNRevision getHeadRevision(SVNURL url,SVNClientManager svnClientManager) { 132 | def wcClient=svnClientManager.getWCClient(); 133 | SVNInfo info = wcClient.doInfo(url, SVNRevision.HEAD, SVNRevision.HEAD); 134 | return info.getRevision(); 135 | } 136 | 137 | private def SVNDirEntry getLatestTag(SVNRepository svnRepository, String branchName) { 138 | def entries = svnRepository.getDir( "tags", -1 , null , (Collection) null ); 139 | SVNDirEntry max=entries.max{it2-> 140 | def matcher=releaseTagPattern.matcher(it2.name); 141 | if (matcher.matches() && branchName.equals(matcher.group(1))) { 142 | Integer.valueOf(matcher.group(2)) 143 | } else { 144 | null 145 | } 146 | } 147 | } 148 | 149 | private def int getNextVersionNumber(SVNDirEntry latestTag) { 150 | if (latestTag==null) { 151 | return 1; 152 | } 153 | def matcher=releaseTagPattern.matcher(latestTag.name) 154 | return matcher.matches() ? Integer.valueOf(matcher.group(2))+1 : 1; 155 | } 156 | 157 | private def void createTag(SVNClientManager svnClientManager, SVNStatus svnStatus, RepoInfo repoInfo, String tagName) { 158 | def tagsUrl=repoInfo.tagsURL 159 | def rev = repoInfo.headRev 160 | def url = svnStatus.URL; 161 | def destURL=tagsUrl.appendPath(tagName,false); 162 | def copySrc=new SVNCopySource[1]; 163 | copySrc[0]=new SVNCopySource(rev,rev,url) 164 | 165 | project.logger.info("Tagging release: $tagName") 166 | def dirsToMake=new SVNURL[1]; 167 | dirsToMake[0]=destURL; 168 | def copyClient=svnClientManager.getCopyClient() 169 | copyClient.doCopy(copySrc,destURL,false,false,true,"Tagging release "+tagName+", (from "+url+", rev "+rev,null) 170 | } 171 | 172 | public boolean isRelease() { 173 | if (release == null) { 174 | throw new GradleException("Can't determine whether this is a release build before the task graph is populated") 175 | } 176 | return release 177 | } 178 | 179 | private class RepoInfo { 180 | private final SVNURL rootURL; 181 | private final String branchName; 182 | private final boolean isBranch; 183 | private final SVNURL tagsURL; 184 | private final SVNRevision headRev; 185 | 186 | RepoInfo(SVNURL rootURL, String branchName, boolean isBranch, SVNURL tagsURL, SVNRevision headRev) { 187 | this.rootURL=rootURL 188 | this.branchName=branchName 189 | this.isBranch=isBranch 190 | this.tagsURL=tagsURL 191 | this.headRev=headRev 192 | } 193 | 194 | def String toString() { 195 | return "rootURL="+rootURL+", "+"branchName="+branchName+", isBranch="+isBranch+", tagsURL="+tagsURL+", headRev="+headRev; 196 | } 197 | } 198 | } -------------------------------------------------------------------------------- /src/main/groovy/no/entitas/gradle/svn/UpToDateChecker.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 no.entitas.gradle.svn 17 | 18 | import org.gradle.api.Project 19 | import org.tmatesoft.svn.core.SVNDepth 20 | import org.tmatesoft.svn.core.SVNException 21 | import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory 22 | import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl 23 | import org.tmatesoft.svn.core.wc.ISVNStatusHandler 24 | import org.tmatesoft.svn.core.wc.SVNClientManager 25 | import org.tmatesoft.svn.core.wc.SVNRevision 26 | import org.tmatesoft.svn.core.wc.SVNStatus 27 | import org.tmatesoft.svn.core.wc.SVNStatusType 28 | 29 | class UpToDateChecker implements ISVNStatusHandler { 30 | boolean remoteModifications=false; 31 | Project project 32 | 33 | UpToDateChecker(Project project) { 34 | this.project = project 35 | } 36 | 37 | public boolean containsRemoteModifications(SVNClientManager svnClientManager, File path, SVNRevision headRev) { 38 | SVNRepositoryFactoryImpl.setup(); 39 | FSRepositoryFactory.setup(); 40 | def statusClient=svnClientManager.getStatusClient() 41 | statusClient.doStatus(path, headRev, SVNDepth.INFINITY, true, true, true, false, this,null) 42 | return remoteModifications 43 | } 44 | 45 | public void handleStatus(SVNStatus status) throws SVNException { 46 | SVNStatusType statusType = status.getRemoteContentsStatus(); 47 | if (statusType!=SVNStatusType.STATUS_NONE && statusType!=SVNStatusType.STATUS_NORMAL && statusType!=SVNStatusType.STATUS_IGNORED) { 48 | project.logger.debug("Remote modifications: $status.file\t$statusType") 49 | remoteModifications=true; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/gitrelease.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2011- 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 | implementation-class=no.entitas.gradle.git.GitReleasePlugin 17 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/svnrelease.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2011- 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 | implementation-class=no.entitas.gradle.svn.SvnReleasePlugin -------------------------------------------------------------------------------- /src/sample/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 | apply plugin: 'gitrelease' 17 | 18 | subprojects { 19 | repositories { 20 | mavenCentral() 21 | } 22 | } 23 | 24 | configure(project(':util')) { 25 | apply plugin: 'java' 26 | version = this.version 27 | release { 28 | failOnSnapshotDependencies = false 29 | versionStrategy = { currentVersion -> 30 | if (System.properties['release.version']) { 31 | System.properties['release.version'] 32 | } else { 33 | new BigDecimal(currentVersion).add(BigDecimal.ONE).toPlainString() 34 | } 35 | } 36 | startVersion = { currentBranch -> "1" } 37 | } 38 | 39 | uploadArchives { 40 | repositories { 41 | flatDir(dirs: file("${buildDir}/repos")) 42 | } 43 | } 44 | } 45 | 46 | configure(project(':main')) { 47 | // TODO should have a test of the release plugin failing when project has SNAPSHOT dependency 48 | apply plugin: 'java' 49 | 50 | dependencies { 51 | // Testing fix for GH-16 52 | compile files (System.getProperty("java.home")+"/../lib/tools.jar") 53 | compile project(":util") 54 | } 55 | } 56 | 57 | buildscript { 58 | dependencies { 59 | classpath fileTree(dir: '../../build/libs', include: '*.jar', excludes: ['*javadoc.jar', '*sources.jar']) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/sample/main/src/main/java/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 | public class Main { 17 | public static void main(String[] args) { 18 | System.out.println("Just a test"); 19 | } 20 | } -------------------------------------------------------------------------------- /src/sample/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 | include 'util' 17 | include 'main' -------------------------------------------------------------------------------- /src/sample/util/src/main/java/Util.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011- 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 | import java.lang.String; 17 | 18 | public class Util { 19 | private String util; 20 | 21 | public Util(String util) { 22 | this.util = util; 23 | } 24 | } -------------------------------------------------------------------------------- /src/test/groovy/no/entitas/gradle/git/GitVersionTest.groovy: -------------------------------------------------------------------------------- 1 | package no.entitas.gradle.git 2 | 3 | import no.entitas.gradle.ReleasePluginExtension 4 | import org.eclipse.jgit.api.Status 5 | import org.eclipse.jgit.lib.Constants 6 | import org.eclipse.jgit.lib.ObjectId 7 | import org.eclipse.jgit.lib.ObjectIdRef.PeeledNonTag 8 | import org.eclipse.jgit.lib.ObjectIdRef.PeeledTag 9 | import org.eclipse.jgit.lib.ObjectReader 10 | import org.eclipse.jgit.lib.Ref 11 | import org.eclipse.jgit.lib.Ref.Storage 12 | import org.eclipse.jgit.lib.Repository 13 | import org.gradle.api.Project 14 | import org.gradle.api.execution.TaskExecutionGraph 15 | import org.gradle.api.invocation.Gradle 16 | import org.gradle.api.logging.Logger 17 | import org.gradle.api.plugins.ExtensionContainer 18 | import spock.lang.Specification 19 | 20 | /** 21 | * Unit test of {@link GitVersion} 22 | * 23 | * @author Stig Kleppe-Jorgensen, 2012.09.30 24 | */ 25 | class GitVersionTest extends Specification { 26 | ObjectId ID_1 = ObjectId.fromString('84f25f6c357612b9b4ff818655d128ef0f62696e') 27 | ObjectId ID_2 = ObjectId.fromString('b7773d13356e2f9623a8e43aaa487ab003c495dd') 28 | ObjectId ID_3 = ObjectId.fromString('3cf221eaf446c56f72230a410bff6c87a95afda1') 29 | 30 | def 'should set SNAPSHOT version for build not on release tag'() { 31 | when: 32 | def gitVersion = createVersion('tag2', ID_2, true, false) 33 | 34 | then: 35 | gitVersion.versionNumber == 'master-SNAPSHOT' 36 | } 37 | 38 | def 'should set SNAPSHOT version for build on release tag with local modifications'() { 39 | when: 40 | def gitVersion = createVersion('master-REL-1', ID_2, false, false) 41 | 42 | then: 43 | gitVersion.versionNumber == 'master-SNAPSHOT' 44 | } 45 | 46 | def 'should set release version for build on release tag without local modifications'() { 47 | when: 48 | def gitVersion = createVersion('master-REL-1', ID_1, true, false) 49 | 50 | then: 51 | gitVersion.versionNumber == 'master-REL-1' 52 | } 53 | 54 | def 'should set next release version for build with task release'() { 55 | when: 56 | def gitVersion = createVersion('master-REL-1', ID_2, true, true) 57 | 58 | then: 59 | gitVersion.versionNumber == 'master-REL-2' 60 | } 61 | 62 | def createVersion(String tagName, ObjectId headId, boolean clean, boolean hasTaskReleasePrepare) { 63 | def gitVersion = new GitVersion(mockForProject(), mockForRepository(tagName, headId)) { 64 | @Override 65 | def workDirStatus(Repository repository) { 66 | null 67 | } 68 | 69 | @Override 70 | boolean branchIsAheadOfRemote() { 71 | false 72 | } 73 | 74 | @Override 75 | def getLatestReleaseTag(String currentBranch) { 76 | 'master-REL-1' 77 | } 78 | } 79 | 80 | def mockStatus = Mock(Status) 81 | mockStatus.clean >> clean 82 | gitVersion.status = mockStatus 83 | 84 | gitVersion.setup(mockForTaskGraph(hasTaskReleasePrepare)) 85 | gitVersion 86 | } 87 | 88 | Repository mockForRepository(def tagName, ObjectId headId) { 89 | Mock(Repository).with { repository -> 90 | repository.getRef(Constants.HEAD) >> new PeeledNonTag(Storage.NEW, 'head', headId) 91 | repository.tags >> ['tag1' : tagRef('ref/tags/tag1'), "${tagName}" : tagRef("ref/tags/${tagName}")] 92 | repository.peel(_) >> { Ref ref -> ref } 93 | repository.fullBranch >> 'refs/heads/master' 94 | repository.newObjectReader() >> Mock(ObjectReader) 95 | repository 96 | } 97 | } 98 | 99 | PeeledTag tagRef(String tag) { 100 | new PeeledTag(Storage.NEW, tag, ID_3, ID_1) 101 | } 102 | 103 | Project mockForProject() { 104 | Mock(Project).with { project -> 105 | project.gradle >> mockForGradle() 106 | project.logger >> Mock(Logger) 107 | project.extensions >> mockForExtensionContainer() 108 | project 109 | } 110 | } 111 | 112 | def mockForExtensionContainer() { 113 | Mock(ExtensionContainer).with { ec -> 114 | ec.getByName(_) >> new ReleasePluginExtension() 115 | ec 116 | } 117 | } 118 | 119 | Gradle mockForGradle() { 120 | Mock(Gradle).with { gradle -> 121 | gradle.taskGraph >> Mock(TaskExecutionGraph) 122 | gradle 123 | } 124 | } 125 | 126 | TaskExecutionGraph mockForTaskGraph(boolean hasTaskReleasePrepare) { 127 | Mock(TaskExecutionGraph).with { taskGraph -> 128 | taskGraph.hasTask(_ as String) >> hasTaskReleasePrepare 129 | taskGraph 130 | } 131 | } 132 | } 133 | --------------------------------------------------------------------------------