From 1a6445f4e2a4ccf9912148ede4d09bcad05ac557 Mon Sep 17 00:00:00 2001 From: John Tipper Date: Wed, 29 Jan 2020 00:02:35 +0000 Subject: [PATCH] #325 #314 Enable multi-module project lib dependency incrementing. --- docs/configuration/basic_usage.md | 14 +- docs/configuration/tasks.md | 14 +- ...uleProjectDependencyIntegrationTest.groovy | 407 ++++++++++++++++++ .../release/ConfigureReleaseTasksTask.groovy | 63 +++ .../axion/release/CreateReleaseTask.groovy | 10 +- .../build/axion/release/ReleasePlugin.groovy | 96 +++++ .../build/axion/release/ReleaseTask.groovy | 10 +- .../infrastructure/DryRepository.groovy | 5 + .../infrastructure/DummyRepository.groovy | 5 + .../infrastructure/config/RulesFactory.groovy | 4 +- .../config/VersionPropertiesFactory.groovy | 5 +- .../release/infrastructure/di/Context.groovy | 9 +- .../di/GradleAwareContext.groovy | 6 +- .../build/axion/release/domain/Releaser.java | 22 +- .../axion/release/domain/VersionFactory.java | 2 +- .../domain/properties/VersionProperties.java | 9 +- .../release/domain/scm/ScmRepository.java | 2 + .../axion/release/domain/scm/ScmService.java | 14 + .../infrastructure/git/GitRepository.java | 32 ++ .../axion/release/domain/ReleaserTest.groovy | 36 +- .../release/domain/VersionFactoryTest.groovy | 12 + .../VersionPropertiesBuilder.groovy | 12 +- .../VersionPropertiesFactoryTest.groovy | 47 +- .../git/GitRepositoryTest.groovy | 27 ++ 24 files changed, 817 insertions(+), 46 deletions(-) create mode 100644 src/integration/groovy/pl/allegro/tech/build/axion/release/MultiModuleProjectDependencyIntegrationTest.groovy create mode 100644 src/main/groovy/pl/allegro/tech/build/axion/release/ConfigureReleaseTasksTask.groovy diff --git a/docs/configuration/basic_usage.md b/docs/configuration/basic_usage.md index d3210232..98c104f2 100644 --- a/docs/configuration/basic_usage.md +++ b/docs/configuration/basic_usage.md @@ -83,7 +83,7 @@ Sometimes it might be desirable to release each module (or just some modules) of multi-module project separately. If so, please make sure that: -- keep in mind, that `scmVersion` must be initialized before +- you keep in mind that `scmVersion` must be initialized before `scmVersion.version` is accessed - apply plugin on each module that should be released on its own @@ -106,6 +106,16 @@ below: For example, within `module`, tags that do not start `module-` will be ignored. +Thus we can configure each subproject within such a multimodule project like this: + +``` +scmVersion { + tag { + prefix = "${project.name}" + } +} +``` + **IMPORTANT:** Note that if the version separator appears in the prefix then tag parsing @@ -150,3 +160,5 @@ versions of any submodules. If this is desired then consider wiring the `create # ./gradlew cV 0.1.0 ``` + +For a multimodule project which has project lib dependencies (e.g. `moduleB` `dependsOn` `project(':moduleA')`), a change within `moduleA`'s code will result in the version of `moduleA` being incremented normally. However, `moduleB`'s code may not have changed and thus it will not have its version incremented, which is usually not the desired behaviour. This issue is rectified by the 2 tasks, `createReleaseDependents` and `releaseDependents`. These tasks are analogous to `createRelease` and `release`, respectively, and traverse the inter-project dependency tree and cause the creation of releases for all projects that have a declared dependency on a project(s) whose code has changed. Thus, for the example just cited, a new release would be created for `moduleB`. This is modelled on how the Java plugin's `buildNeeded` and `buildDependents` tasks work (described [here](https://docs.gradle.org/current/userguide/multi_project_builds.html#sec:multiproject_build_and_test)). diff --git a/docs/configuration/tasks.md b/docs/configuration/tasks.md index b3034728..d3020d1f 100644 --- a/docs/configuration/tasks.md +++ b/docs/configuration/tasks.md @@ -1,14 +1,18 @@ # Tasks -`axion-gradle-plugin` adds 6 new Gradle tasks: +`axion-gradle-plugin` adds 8* new Gradle tasks: - *verifyRelease* - *release* - *createRelease* +- *releaseDependents* +- *createReleaseDependents* - *pushRelease* - *currentVersion* - *markNextVersion* +*The plugin also creates a helper task called `configureReleaseDependentsTasks` that is called internally, but this is not designed to be called by users. + ## verifyRelease Runs all checks before release. **release** task depends on it. See @@ -29,6 +33,14 @@ of these tasks running exactly one after another. Run pre-release actions ([Pre/post release hooks](hooks.md)) and create release tag. +## releaseDependents + +Works as per `release`, except that releases are created on any submodules that depend on the project in which this task is called, including transitive dependencies (i.e. changes to `moduleA` will generate releases for `moduleB` and `moduleC`, assuming `moduleC` depends on `moduleB` depends on `moduleA`). This is described in further detail in [Basic Usage](../basic_usage.md). + +## createReleaseDependents + +Works as per `createRelease`, except that releases are created on any submodules that depend on the project in which this task is called, including transitive dependencies (i.e. changes to `moduleA` will generate releases for `moduleB` and `moduleC`, assuming `moduleC` depends on `moduleB` depends on `moduleA`). This is described in further detail in [Basic Usage](../basic_usage.md). + ## pushRelease Push tag to remote. diff --git a/src/integration/groovy/pl/allegro/tech/build/axion/release/MultiModuleProjectDependencyIntegrationTest.groovy b/src/integration/groovy/pl/allegro/tech/build/axion/release/MultiModuleProjectDependencyIntegrationTest.groovy new file mode 100644 index 00000000..7a482950 --- /dev/null +++ b/src/integration/groovy/pl/allegro/tech/build/axion/release/MultiModuleProjectDependencyIntegrationTest.groovy @@ -0,0 +1,407 @@ +package pl.allegro.tech.build.axion.release + +import org.gradle.testkit.runner.TaskOutcome + +import java.util.stream.Collectors + +class MultiModuleProjectDependencyIntegrationTest extends BaseIntegrationTest { + + File getSubmoduleDir(String projectName) { + return new File(temporaryFolder.root, projectName) + } + + void generateSubmoduleBuildFile(String projectName) { + File submoduleDir = getSubmoduleDir(projectName) + submoduleDir.mkdirs() + File buildFile = new File(submoduleDir, "build.gradle") + buildFile << """ + plugins { + id 'pl.allegro.tech.build.axion-release' + } + + scmVersion { + tag { + prefix = '${projectName}' + } + } + + project.version = scmVersion.version + """ + } + + void generateSettingsFile(File dir, List submodules) { + String submodulesIncludeString = submodules.stream().map({ m -> "'${m}'" }).collect(Collectors.joining(',')) + File settings = new File(dir, "settings.gradle") + settings << """ + include ${submodulesIncludeString} + + """ + } + + void generateGitIgnoreFile(File dir) { + File gitIgnore = new File(dir, ".gitignore") + gitIgnore << """\ + .gradle + """.stripIndent() + } + + /** + * Configure the multi-module project for testing. + * We start off with v1.0.0 on the parent project and versions 2.0.0, 3.0.0 and 4.0.0 for the 3 child projects. + * project2 depends on project1, project3 depends on project2 + */ + void initialProjectConfiguration() { + List submodules = ["project1", "project2", "project3"] + buildFile(''' + plugins { + id 'java' + } + scmVersion { + monorepos { + projectDirs = project.subprojects.collect({p -> p.name}) + } + } + subprojects { + apply plugin: 'java' + } + project(":project2") { + dependencies { + implementation project(':project1') + } + } + project(":project3") { + dependencies { + implementation project(':project2') + } + } + ''' + ) + generateSettingsFile(temporaryFolder.root, submodules) + generateGitIgnoreFile(temporaryFolder.root) + + repository.commit(['.'], "initial commit of top level project") + + // create version for main project + runGradle(':createRelease', '-Prelease.version=1.0.0', '-Prelease.disableChecks') + + // create submodules + int versionCounter = 2 + for (String module : submodules) { + // generate the project files + generateSubmoduleBuildFile(module) + // add to repo + repository.commit(["${module}".toString()], "commit submodule ${module}".toString()) + // tag release for that project + runGradle(":${module}:createRelease", "-Prelease.version=${versionCounter}.0.0", '-Prelease.disableChecks') + versionCounter++ + } + } + + def "plugin can distinguish between submodules versions"() { + given: + initialProjectConfiguration() + + when: + def result = runGradle(':currentVersion') + def match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '1.0.0' + result.task(":currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project1:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '2.0.0' + result.task(":project1:currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project2:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '3.0.0' + result.task(":project2:currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project3:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '4.0.0' + result.task(":project3:currentVersion").outcome == TaskOutcome.SUCCESS + + } + + def "releaseDependents and change to dependent submodule should not change root or upstream submodule versions"() { + given: + initialProjectConfiguration() + File dummy = new File(getSubmoduleDir("project2"), "dummy.txt") + dummy.createNewFile() + repository.commit(["project2"], "commit submodule project2") + runGradle(":project2:releaseDependents", "-Prelease.version=5.0.0", '-Prelease.localOnly', '-Prelease.disableChecks') + + when: + def result = runGradle(':currentVersion') + def match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '1.0.0' + result.task(":currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project1:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '2.0.0' + result.task(":project1:currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project2:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '5.0.0' + result.task(":project2:currentVersion").outcome == TaskOutcome.SUCCESS + + } + + def "releaseDependents and change to upstream submodule should force increment dependent module version despite no changes there"() { + given: + initialProjectConfiguration() + File dummy = new File(getSubmoduleDir("project1"), "dummy.txt") + dummy.createNewFile() + repository.commit(["project1"], "commit submodule project1") + runGradle(":project1:releaseDependents", '-Prelease.localOnly', '-Prelease.disableChecks') + + when: + def result = runGradle(':currentVersion') + def match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '1.0.0' + result.task(":currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project1:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '2.0.1' + result.task(":project1:currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project2:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '3.0.1' + result.task(":project2:currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project3:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '4.0.1' + result.task(":project3:currentVersion").outcome == TaskOutcome.SUCCESS + + } + + def "releaseDependents and change to submodule should not change root project version"() { + given: + initialProjectConfiguration() + File dummy = new File(getSubmoduleDir("project1"), "dummy.txt") + dummy.createNewFile() + repository.commit(["project1"], "commit submodule project1") + runGradle(":project1:releaseDependents", "-Prelease.version=4.0.0", '-Prelease.localOnly', '-Prelease.disableChecks') + + when: + def result = runGradle(':currentVersion') + def match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '1.0.0' + result.task(":currentVersion").outcome == TaskOutcome.SUCCESS + } + + def "releaseDependents and change to root project should not change child project versions"() { + given: + initialProjectConfiguration() + File dummy = new File(temporaryFolder.root, "dummy.txt") + dummy.createNewFile() + repository.commit(["dummy.txt"], "commit parent project") + runGradle(":releaseDependents", "-Prelease.version=4.0.0", '-Prelease.localOnly', '-Prelease.disableChecks') + + when: + def result = runGradle(':currentVersion') + def match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '4.0.0' + result.task(":currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project1:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '2.0.0' + result.task(":project1:currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project2:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '3.0.0' + result.task(":project2:currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project3:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '4.0.0' + result.task(":project3:currentVersion").outcome == TaskOutcome.SUCCESS + + } + + def "createReleaseDependents and change to dependent submodule should not change root or upstream submodule versions"() { + given: + initialProjectConfiguration() + File dummy = new File(getSubmoduleDir("project2"), "dummy.txt") + dummy.createNewFile() + repository.commit(["project2"], "commit submodule project2") + runGradle(":project2:createReleaseDependents", "-Prelease.version=5.0.0", '-Prelease.disableChecks') + + when: + def result = runGradle(':currentVersion') + def match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '1.0.0' + result.task(":currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project1:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '2.0.0' + result.task(":project1:currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project2:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '5.0.0' + result.task(":project2:currentVersion").outcome == TaskOutcome.SUCCESS + + } + + def "createReleaseDependents and change to upstream submodule should force increment dependent module version despite no changes there"() { + given: + initialProjectConfiguration() + File dummy = new File(getSubmoduleDir("project1"), "dummy.txt") + dummy.createNewFile() + repository.commit(["project1"], "commit submodule project1") + runGradle(":project1:createReleaseDependents", '-Prelease.disableChecks') + + when: + def result = runGradle(':currentVersion') + def match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '1.0.0' + result.task(":currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project1:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '2.0.1' + result.task(":project1:currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project2:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '3.0.1' + result.task(":project2:currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project3:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '4.0.1' + result.task(":project3:currentVersion").outcome == TaskOutcome.SUCCESS + + } + + def "createReleaseDependents and change to submodule should not change root project version"() { + given: + initialProjectConfiguration() + File dummy = new File(getSubmoduleDir("project1"), "dummy.txt") + dummy.createNewFile() + repository.commit(["project1"], "commit submodule project1") + runGradle(":project1:createReleaseDependents", "-Prelease.version=4.0.0", '-Prelease.disableChecks') + + when: + def result = runGradle(':currentVersion') + def match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '1.0.0' + result.task(":currentVersion").outcome == TaskOutcome.SUCCESS + } + + def "createReleaseDependents and change to root project should not change child project versions"() { + given: + initialProjectConfiguration() + File dummy = new File(temporaryFolder.root, "dummy.txt") + dummy.createNewFile() + repository.commit(["dummy.txt"], "commit parent project") + runGradle(":createReleaseDependents", "-Prelease.version=4.0.0", '-Prelease.disableChecks') + + when: + def result = runGradle(':currentVersion') + def match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '4.0.0' + result.task(":currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project1:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '2.0.0' + result.task(":project1:currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project2:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '3.0.0' + result.task(":project2:currentVersion").outcome == TaskOutcome.SUCCESS + + when: + result = runGradle(':project3:currentVersion') + match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '4.0.0' + result.task(":project3:currentVersion").outcome == TaskOutcome.SUCCESS + + } +} diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/ConfigureReleaseTasksTask.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/ConfigureReleaseTasksTask.groovy new file mode 100644 index 00000000..e69f30a9 --- /dev/null +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/ConfigureReleaseTasksTask.groovy @@ -0,0 +1,63 @@ +package pl.allegro.tech.build.axion.release + +import org.gradle.api.DefaultTask +import org.gradle.api.Task +import org.gradle.api.artifacts.Configuration +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.TaskDependency +import pl.allegro.tech.build.axion.release.domain.VersionConfig +import pl.allegro.tech.build.axion.release.domain.VersionContext +import pl.allegro.tech.build.axion.release.domain.VersionService +import pl.allegro.tech.build.axion.release.infrastructure.di.Context +import pl.allegro.tech.build.axion.release.infrastructure.di.GradleAwareContext + +/** + * Task that should set forceIncrementVersion within CreateReleaseTask and ReleaseTask. + */ +class ConfigureReleaseTasksTask extends DefaultTask { + + @Optional + VersionConfig versionConfig + + @TaskAction + void configureReleaseTasks() { + // by default, do not increment the version + ext.shouldForceIncrementVersion = false + + // determine if there are any changes in this project + VersionConfig config = GradleAwareContext.configOrCreateFromProject(project, versionConfig) + Context context = GradleAwareContext.create(project, config) + VersionService versionService = context.versionService() + VersionContext versionContext = versionService.currentVersion( + context.rules().getVersion(), context.rules().getTag(), context.rules().getNextVersion() + ) + if (versionContext.isSnapshot()) { + ext.shouldForceIncrementVersion = true + } else { + // if there are no changes in this project then we need to check upstream projects for changes + final Configuration configuration = project.getConfigurations().findByName(ReleasePlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME) + if (configuration != null) { + TaskDependency taskDependency = configuration.getTaskDependencyFromProjectDependency(true, ReleasePlugin.CONFIGURE_RELEASE_DEPENDENTS_TASKS_TASK) + for (Task depTask : taskDependency.getDependencies()) { + if (depTask.shouldForceIncrementVersion) { + // upstream change detected, we should increment the version, no need to check any more + ext.shouldForceIncrementVersion = true + break + } + } + } + } + + // now configure the release tasks to force increment the version if a change was detected upstream + // if there was a change in this project then the project will be released normally, not forced + if (!versionContext.isSnapshot()) { + ReleaseTask releaseTask = (ReleaseTask) getProject().tasks.findByName(ReleasePlugin.RELEASE_TASK) + releaseTask?.setForceIncrementVersion(shouldForceIncrementVersion) + + CreateReleaseTask createReleaseTask = (CreateReleaseTask) getProject().tasks.findByName(ReleasePlugin.CREATE_RELEASE_TASK) + createReleaseTask?.setForceIncrementVersion(shouldForceIncrementVersion) + } + } + +} diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/CreateReleaseTask.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/CreateReleaseTask.groovy index b9043865..2c25cd78 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/CreateReleaseTask.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/CreateReleaseTask.groovy @@ -10,15 +10,21 @@ import pl.allegro.tech.build.axion.release.infrastructure.di.GradleAwareContext class CreateReleaseTask extends DefaultTask { + boolean forceIncrementVersion = false + @Optional VersionConfig versionConfig @TaskAction void release() { VersionConfig config = GradleAwareContext.configOrCreateFromProject(project, versionConfig) - Context context = GradleAwareContext.create(project, config) + Context context = GradleAwareContext.create(project, config, forceIncrementVersion) Releaser releaser = context.releaser() - releaser.release(context.rules()) + println "forceIncrementVersion " + forceIncrementVersion + releaser.release(context.projectRootRelativePath(), context.rules(), forceIncrementVersion) } + void setForceIncrementVersion(boolean forceIncrementVersion) { + this.forceIncrementVersion = forceIncrementVersion + } } diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/ReleasePlugin.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/ReleasePlugin.groovy index c19130c9..c05c829e 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/ReleasePlugin.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/ReleasePlugin.groovy @@ -1,8 +1,10 @@ package pl.allegro.tech.build.axion.release +import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.artifacts.Configuration import pl.allegro.tech.build.axion.release.domain.VersionConfig class ReleasePlugin implements Plugin { @@ -23,6 +25,16 @@ class ReleasePlugin implements Plugin { public static final String DRY_RUN_FLAG = 'release.dryRun' + private static final String RELEASE_GROUP = "Release" + + public static final String RELEASE_DEPENDENTS_TASK = "releaseDependents" + + public static final String CREATE_RELEASE_DEPENDENTS_TASK = "createReleaseDependents" + + public static final String CONFIGURE_RELEASE_DEPENDENTS_TASKS_TASK = "configureReleaseDependentsTasks" + + public static final String TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME = "testRuntimeClasspath"; + @Override void apply(Project project) { project.extensions.create(VERSION_EXTENSION, VersionConfig, project) @@ -52,5 +64,89 @@ class ReleasePlugin implements Plugin { Task currentVersionTask = project.tasks.create(CURRENT_VERSION_TASK, OutputCurrentVersionTask) currentVersionTask.group = 'Help' currentVersionTask.description = 'Prints current project version extracted from SCM.' + + registerReleaseDependentTasks(project) + + registerConfigureReleaseTasksTask(project) } + + /** + * Create releaseDependents and createReleaseDependents tasks. + * The tasks should depend on the release task and be set as a dependency for all project tasks that + * depend on this project. + * @param project the project in which to register the tasks. + */ + void registerReleaseDependentTasks(Project project) { + project.getTasks().register(CREATE_RELEASE_DEPENDENTS_TASK, new Action() { + @Override + void execute(Task task) { + task.setDescription("Creates a release for this project and all dependent projects") + task.setGroup(RELEASE_GROUP) + task.dependsOn(CREATE_RELEASE_TASK) + addDependsOnTaskInOtherProjects(task, false, + CREATE_RELEASE_DEPENDENTS_TASK, TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME) + // configure this task to depend on configureReleaseDependentsTasks + task.dependsOn(CONFIGURE_RELEASE_DEPENDENTS_TASKS_TASK) + } + }) + project.getTasks().register(RELEASE_DEPENDENTS_TASK, new Action() { + @Override + void execute(Task task) { + task.setDescription("Creates and pushes a release for this project and all dependent projects") + task.setGroup(RELEASE_GROUP) + task.dependsOn(RELEASE_TASK) + addDependsOnTaskInOtherProjects(task, false, + RELEASE_DEPENDENTS_TASK, TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME) + // configure this task to depend on configureReleaseDependentsTasks + task.dependsOn(CONFIGURE_RELEASE_DEPENDENTS_TASKS_TASK) + } + }) + } + + /** + * Register configureReleaseDependentsTasks task. + * The task will fire before release. + * @param project the project in which to register the tasks. + */ + void registerConfigureReleaseTasksTask(Project project) { + project.getTasks().register(CONFIGURE_RELEASE_DEPENDENTS_TASKS_TASK, ConfigureReleaseTasksTask, new Action() { + @Override + void execute(Task task) { + task.setDescription("Configures the releaseDependents and createReleaseDependents tasks to increment the release version") + task.setGroup(RELEASE_GROUP) + + // createReleaseDependents will cause createRelease to fire, so set this task to fire before createRelease + task.getProject().getTasks().findByName(CREATE_RELEASE_TASK).configure { + mustRunAfter(task) + } + // releaseDependents will cause release to fire, so set this task to fire before release + task.getProject().getTasks().findByName(RELEASE_TASK).configure { + mustRunAfter(task) + } + // configure this task to depend on all similar tasks in upstream projects + addDependsOnTaskInOtherProjects(task, true, + CONFIGURE_RELEASE_DEPENDENTS_TASKS_TASK, TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME) + } + }) + } + + /** + * Adds a dependency on tasks with the specified name in other projects. The other projects are determined from + * project lib dependencies using the specified configuration name. These may be projects this project depends on or + * projects that depend on this project based on the useDependOn argument. + * + * @param task Task to add dependencies to + * @param useDependedOn if true, add tasks from projects this project depends on, otherwise use projects that depend on this one. + * @param otherProjectTaskName name of task in other projects + * @param configurationName name of configuration to use to find the other projects. If the configuration does not exist then no dependency will be added. + */ + private void addDependsOnTaskInOtherProjects(final Task task, boolean useDependedOn, String otherProjectTaskName, + String configurationName) { + Project project = task.getProject() + final Configuration configuration = project.getConfigurations().findByName(configurationName) + if (configuration != null) { + task.dependsOn(configuration.getTaskDependencyFromProjectDependency(useDependedOn, otherProjectTaskName)) + } + } + } diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/ReleaseTask.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/ReleaseTask.groovy index 36974435..3f45acf5 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/ReleaseTask.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/ReleaseTask.groovy @@ -11,15 +11,17 @@ import pl.allegro.tech.build.axion.release.infrastructure.di.GradleAwareContext class ReleaseTask extends DefaultTask { + boolean forceIncrementVersion = false + @Optional VersionConfig versionConfig @TaskAction void release() { VersionConfig config = GradleAwareContext.configOrCreateFromProject(project, versionConfig) - Context context = GradleAwareContext.create(project, config) + Context context = GradleAwareContext.create(project, config, forceIncrementVersion) Releaser releaser = context.releaser() - ScmPushResult result = releaser.releaseAndPush(context.rules()) + ScmPushResult result = releaser.releaseAndPush(context.projectRootRelativePath(), context.rules(), forceIncrementVersion) if(!result.success) { def message = result.remoteMessage.orElse("Unknown error during push") @@ -31,4 +33,8 @@ class ReleaseTask extends DefaultTask { void setVersionConfig(VersionConfig versionConfig) { this.versionConfig = versionConfig } + + void setForceIncrementVersion(boolean forceIncrementVersion) { + this.forceIncrementVersion = forceIncrementVersion + } } diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/DryRepository.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/DryRepository.groovy index 736c5fba..c4f79117 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/DryRepository.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/DryRepository.groovy @@ -31,6 +31,11 @@ class DryRepository implements ScmRepository { log("creating tag with name: $tagName") } + @Override + void tagOnCommit(String revision, String tagName) { + log("creating tag with name: $tagName on commit ${revision}") + } + @Override void dropTag(String tagName) { log("dropping tag with name: $tagName") diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/DummyRepository.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/DummyRepository.groovy index 555fa4bd..556c35cc 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/DummyRepository.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/DummyRepository.groovy @@ -31,6 +31,11 @@ class DummyRepository implements ScmRepository { log('create tag') } + @Override + void tagOnCommit(String revision, String tagName) { + log('create tag') + } + @Override void dropTag(String tagName) { log('drop tag') diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/RulesFactory.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/RulesFactory.groovy index 0e93dcb3..c0428b39 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/RulesFactory.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/RulesFactory.groovy @@ -8,10 +8,10 @@ import pl.allegro.tech.build.axion.release.domain.scm.ScmPosition class RulesFactory { - static Properties create(Project project, VersionConfig versionConfig, ScmPosition position) { + static Properties create(Project project, VersionConfig versionConfig, ScmPosition position, boolean shouldForceIncrementVersion) { return new Properties( project.hasProperty(ReleasePlugin.DRY_RUN_FLAG), - VersionPropertiesFactory.create(project, versionConfig, position.branch), + VersionPropertiesFactory.create(project, versionConfig, position.branch, shouldForceIncrementVersion), TagPropertiesFactory.create(versionConfig.tag, position.branch), ChecksPropertiesFactory.create(project, versionConfig.checks), NextVersionPropertiesFactory.create(project, versionConfig.nextVersion), diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/VersionPropertiesFactory.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/VersionPropertiesFactory.groovy index cf00ff02..f1b413b4 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/VersionPropertiesFactory.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/VersionPropertiesFactory.groovy @@ -24,7 +24,7 @@ class VersionPropertiesFactory { private static final String VERSION_CREATOR_PROPERTY = 'release.versionCreator' - static VersionProperties create(Project project, VersionConfig config, String currentBranch) { + static VersionProperties create(Project project, VersionConfig config, String currentBranch, boolean shouldForceIncrementVersion) { String forceVersionValue = project.hasProperty(FORCE_VERSION_PROPERTY) ? project.property(FORCE_VERSION_PROPERTY) : null if (forceVersionValue == null) { forceVersionValue = project.hasProperty(DEPRECATED_FORCE_VERSION_PROPERTY) ? project.property(DEPRECATED_FORCE_VERSION_PROPERTY) : null @@ -43,7 +43,8 @@ class VersionPropertiesFactory { findVersionIncrementer(project, config, currentBranch), config.sanitizeVersion, useHighestVersion, - MonorepoPropertiesFactory.create(project, config.monorepoConfig, currentBranch) + MonorepoPropertiesFactory.create(project, config.monorepoConfig, currentBranch), + shouldForceIncrementVersion ) } diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/di/Context.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/di/Context.groovy index d5c6ecae..010863c7 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/di/Context.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/di/Context.groovy @@ -23,14 +23,17 @@ class Context { private final LocalOnlyResolver localOnlyResolver + private final String projectRootRelativePath + Context(Properties rules, ScmRepository scmRepository, ScmProperties scmProperties, File projectRoot, LocalOnlyResolver localOnlyResolver) { this.rules = rules this.scmRepository = scmRepository this.scmProperties = scmProperties this.localOnlyResolver = localOnlyResolver + this.projectRootRelativePath = scmProperties.directory.toPath().relativize(projectRoot.toPath()).toString() instances[ScmRepository] = scmRepository - instances[VersionService] = new VersionService(new VersionResolver(scmRepository, scmProperties.directory.toPath().relativize(projectRoot.toPath()).toString())) + instances[VersionService] = new VersionService(new VersionResolver(scmRepository, projectRootRelativePath)) } private T get(Class clazz) { @@ -68,4 +71,8 @@ class Context { ScmChangesPrinter changesPrinter() { return new GitChangesPrinter(get(ScmRepository) as GitRepository) } + + String projectRootRelativePath() { + return projectRootRelativePath + } } diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/di/GradleAwareContext.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/di/GradleAwareContext.groovy index 68420e7e..fc71dbb9 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/di/GradleAwareContext.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/di/GradleAwareContext.groovy @@ -11,11 +11,15 @@ import pl.allegro.tech.build.axion.release.infrastructure.config.ScmPropertiesFa class GradleAwareContext { static Context create(Project project, VersionConfig versionConfig) { + return create(project, versionConfig, false) + } + + static Context create(Project project, VersionConfig versionConfig, boolean shouldForceIncrementVersion) { ScmProperties scmProperties = ScmPropertiesFactory.create(project, versionConfig) ScmRepository scmRepository = ScmRepositoryFactory.create(scmProperties) return new Context( - RulesFactory.create(project, versionConfig, scmRepository.currentPosition()), + RulesFactory.create(project, versionConfig, scmRepository.currentPosition(), shouldForceIncrementVersion), scmRepository, scmProperties, project.projectDir, diff --git a/src/main/java/pl/allegro/tech/build/axion/release/domain/Releaser.java b/src/main/java/pl/allegro/tech/build/axion/release/domain/Releaser.java index 29c50e31..1592a09d 100644 --- a/src/main/java/pl/allegro/tech/build/axion/release/domain/Releaser.java +++ b/src/main/java/pl/allegro/tech/build/axion/release/domain/Releaser.java @@ -4,6 +4,7 @@ import pl.allegro.tech.build.axion.release.domain.hooks.ReleaseHooksRunner; import pl.allegro.tech.build.axion.release.domain.logging.ReleaseLogger; import pl.allegro.tech.build.axion.release.domain.properties.Properties; +import pl.allegro.tech.build.axion.release.domain.scm.ScmPosition; import pl.allegro.tech.build.axion.release.domain.scm.ScmPushResult; import pl.allegro.tech.build.axion.release.domain.scm.ScmService; @@ -22,19 +23,29 @@ public Releaser(VersionService versionService, ScmService repository, ReleaseHoo this.hooksRunner = hooksRunner; } - public Optional release(Properties properties) { + public Optional release(String projectRootRelativePath, Properties properties, boolean shouldForceIncrement) { VersionContext versionContext = versionService.currentVersion( properties.getVersion(), properties.getTag(), properties.getNextVersion() ); Version version = versionContext.getVersion(); - if (versionContext.isSnapshot()) { + if (versionContext.isSnapshot() || shouldForceIncrement) { String tagName = properties.getTag().getSerialize().call(properties.getTag(), version.toString()); hooksRunner.runPreReleaseHooks(properties.getHooks(), properties, versionContext, version); logger.quiet("Creating tag: " + tagName); - repository.tag(tagName); + // if snapshot then release normally, otherwise release tag on last commit that is relevant to this project + if (versionContext.isSnapshot()) { + System.out.println("isSnapshot"); + repository.tag(tagName); + } else { + System.out.println("not isSnapshot"); + + repository.tagOnCommit(repository.positionOfLastChangeIn(projectRootRelativePath, + properties.getVersion().getMonorepoProperties().getDirsToExclude() + ).getRevision(), tagName); + } hooksRunner.runPostReleaseHooks(properties.getHooks(), properties, versionContext, version); return Optional.of(tagName); @@ -42,11 +53,10 @@ public Optional release(Properties properties) { logger.quiet("Working on released version " + version + ", nothing to release"); return Optional.empty(); } - } - public ScmPushResult releaseAndPush(Properties rules) { - Optional releasedTagName = release(rules); + public ScmPushResult releaseAndPush(String projectRootRelativePath, Properties rules, boolean shouldForceIncrementVersion) { + Optional releasedTagName = release(projectRootRelativePath, rules, shouldForceIncrementVersion); ScmPushResult result = pushRelease(); diff --git a/src/main/java/pl/allegro/tech/build/axion/release/domain/VersionFactory.java b/src/main/java/pl/allegro/tech/build/axion/release/domain/VersionFactory.java index 64fd832b..89a73ea1 100644 --- a/src/main/java/pl/allegro/tech/build/axion/release/domain/VersionFactory.java +++ b/src/main/java/pl/allegro/tech/build/axion/release/domain/VersionFactory.java @@ -59,7 +59,7 @@ public FinalVersion createFinalVersion(ScmState scmState, Version version) { boolean isSnapshot = forceVersionShouldForceSnapshot || versionProperties.isForceSnapshot() || hasChanges || scmState.isOnNextVersionTag() || scmState.isNoReleaseTagsFound(); boolean proposedVersionIsAlreadySnapshot = scmState.isOnNextVersionTag() || scmState.isNoReleaseTagsFound(); - boolean incrementVersion = ((versionProperties.isForceSnapshot() || hasChanges) && !proposedVersionIsAlreadySnapshot); + boolean incrementVersion = ((versionProperties.isForceSnapshot() || hasChanges || versionProperties.isShouldForceIncrementVersion()) && !proposedVersionIsAlreadySnapshot); Version finalVersion = version; if (StringGroovyMethods.asBoolean(versionProperties.getForcedVersion())) { diff --git a/src/main/java/pl/allegro/tech/build/axion/release/domain/properties/VersionProperties.java b/src/main/java/pl/allegro/tech/build/axion/release/domain/properties/VersionProperties.java index b5a0fb34..096e7f51 100644 --- a/src/main/java/pl/allegro/tech/build/axion/release/domain/properties/VersionProperties.java +++ b/src/main/java/pl/allegro/tech/build/axion/release/domain/properties/VersionProperties.java @@ -13,6 +13,7 @@ public class VersionProperties { private final boolean sanitizeVersion; private final boolean useHighestVersion; private final MonorepoProperties monorepoProperties; + private final boolean shouldForceIncrementVersion; public VersionProperties( String forcedVersion, @@ -22,7 +23,8 @@ public VersionProperties( Closure versionIncrementer, boolean sanitizeVersion, boolean useHighestVersion, - MonorepoProperties monorepoProperties + MonorepoProperties monorepoProperties, + boolean shouldForceIncrementVersion ) { this.forcedVersion = forcedVersion; this.forceSnapshot = forceSnapshot; @@ -32,6 +34,7 @@ public VersionProperties( this.sanitizeVersion = sanitizeVersion; this.useHighestVersion = useHighestVersion; this.monorepoProperties = monorepoProperties; + this.shouldForceIncrementVersion = shouldForceIncrementVersion; } public boolean forceVersion() { @@ -69,4 +72,8 @@ public final boolean isUseHighestVersion() { public MonorepoProperties getMonorepoProperties() { return monorepoProperties; } + + public boolean isShouldForceIncrementVersion() { + return shouldForceIncrementVersion; + } } diff --git a/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmRepository.java b/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmRepository.java index e77d2f23..d460d100 100644 --- a/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmRepository.java +++ b/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmRepository.java @@ -9,6 +9,8 @@ public interface ScmRepository { void tag(String tagName); + void tagOnCommit(String revision, String tagName); + void dropTag(String tagName); ScmPushResult push(ScmIdentity identity, ScmPushOptions pushOptions); diff --git a/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmService.java b/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmService.java index 9adb389e..741a905d 100644 --- a/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmService.java +++ b/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmService.java @@ -23,6 +23,10 @@ public void tag(String tagName) { repository.tag(tagName); } + public void tagOnCommit(String revision, String tagName) { + repository.tagOnCommit(revision, tagName); + } + public void dropTag(String tagName) { try { repository.dropTag(tagName); @@ -52,6 +56,16 @@ public ScmPosition position() { return repository.currentPosition(); } + /** + * Find the last commit that occurred within a certain subdirectory of the project. + * @param path Path to look at for commits + * @param excludeSubFolders Subfolders within the search path to ignore, generally because they are subprojects themselves. + * @return ScmPosition representing the last commit in the given search path. + */ + public ScmPosition positionOfLastChangeIn(String path, List excludeSubFolders) { + return repository.positionOfLastChangeIn(path, excludeSubFolders); + } + public void commit(List patterns, String message) { repository.commit(patterns, message); } diff --git a/src/main/java/pl/allegro/tech/build/axion/release/infrastructure/git/GitRepository.java b/src/main/java/pl/allegro/tech/build/axion/release/infrastructure/git/GitRepository.java index 4f579be9..28c7c5b0 100644 --- a/src/main/java/pl/allegro/tech/build/axion/release/infrastructure/git/GitRepository.java +++ b/src/main/java/pl/allegro/tech/build/axion/release/infrastructure/git/GitRepository.java @@ -101,6 +101,38 @@ public void tag(final String tagName) { } } + @Override + public void tagOnCommit(final String revision, final String tagName) { + try { + ObjectId commitId = ObjectId.fromString(revision); + RevWalk revWalk = new RevWalk(jgitRepository.getRepository()); + RevCommit commit = revWalk.parseCommit(commitId); + + List tags = jgitRepository.tagList().call(); + + boolean isOnExistingTag = tags.stream().anyMatch(ref -> { + boolean onTag = ref.getName().equals(GIT_TAG_PREFIX + tagName); + boolean onRef; + try { + onRef = jgitRepository.getRepository().getRefDatabase() + .peel(ref).getPeeledObjectId().getName().equals(revision); + } catch (IOException e) { + throw new ScmException(e); + } + + return onTag && onRef; + }); + + if (!isOnExistingTag) { + jgitRepository.tag().setObjectId(commit).setName(tagName).call(); + } else { + logger.debug("The commit " + revision + " already has the tag " + tagName + "."); + } + } catch (GitAPIException | IOException e) { + throw new ScmException(e); + } + } + private ObjectId head() throws IOException { return jgitRepository.getRepository().resolve(Constants.HEAD); } diff --git a/src/test/groovy/pl/allegro/tech/build/axion/release/domain/ReleaserTest.groovy b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/ReleaserTest.groovy index 0ccd8b81..f7b35f9c 100644 --- a/src/test/groovy/pl/allegro/tech/build/axion/release/domain/ReleaserTest.groovy +++ b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/ReleaserTest.groovy @@ -28,7 +28,7 @@ class ReleaserTest extends RepositoryBasedTest { .build() when: - releaser.release(rules) + releaser.release(context.projectRootRelativePath(), rules, false) then: currentVersion() == '2.0.0' @@ -39,7 +39,18 @@ class ReleaserTest extends RepositoryBasedTest { repository.tag('release-1.0.0') when: - releaser.release(context.rules()) + releaser.release(context.projectRootRelativePath(), context.rules(), false) + + then: + currentVersion() == '1.0.0' + } + + def "should release version when on tag and shouldForceIncrement"() { + given: + repository.tag('release-1.0.0') + + when: + releaser.release(context.projectRootRelativePath(), context.rules(), true) then: currentVersion() == '1.0.0' @@ -53,7 +64,7 @@ class ReleaserTest extends RepositoryBasedTest { .build() when: - releaser.release(rules) + releaser.release(context.projectRootRelativePath(), rules, false) then: currentVersion() == '3.0.0-rc4' @@ -64,7 +75,18 @@ class ReleaserTest extends RepositoryBasedTest { repository.tag('release-3.0.0-rc4') when: - releaser.release(context.rules()) + releaser.release(context.projectRootRelativePath(), context.rules(), false) + + then: + currentVersion() == '3.0.0-rc4' + } + + def "should release version when on pre-released version tag and shouldForceIncrement"() { + given: + repository.tag('release-3.0.0-rc4') + + when: + releaser.release(context.projectRootRelativePath(), context.rules(), true) then: currentVersion() == '3.0.0-rc4' @@ -76,7 +98,7 @@ class ReleaserTest extends RepositoryBasedTest { repository.commit(['*'], 'make is snapshot') when: - releaser.release(context.rules()) + releaser.release(context.projectRootRelativePath(), context.rules(), false) then: currentVersion() == '3.0.1' @@ -90,7 +112,7 @@ class ReleaserTest extends RepositoryBasedTest { .build() when: - releaser.release(rules) + releaser.release(context.projectRootRelativePath(), rules, false) then: currentVersion() == '3.0.0' @@ -106,7 +128,7 @@ class ReleaserTest extends RepositoryBasedTest { .build() when: - releaser.release(rules) + releaser.release(context.projectRootRelativePath(), rules, false) then: currentVersion() == '3.2.0' diff --git a/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionFactoryTest.groovy b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionFactoryTest.groovy index a64dd535..4c9813fd 100644 --- a/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionFactoryTest.groovy +++ b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionFactoryTest.groovy @@ -108,6 +108,18 @@ class VersionFactoryTest extends Specification { version.snapshot } + def "should increment patch version when on release tag if forceIncrementVersion is set"() { + given: + VersionFactory factory = versionFactory(versionProperties().forceIncrementVersion().build()) + + when: + VersionFactory.FinalVersion version = factory.createFinalVersion(scmState().onReleaseTag().build(), Version.valueOf('1.0.0')) + + then: + version.version.toString() == '1.0.1' + !version.snapshot + } + def "should not increment patch version when on tag and there are uncommitted changes but they are ignored (default)"() { when: VersionFactory.FinalVersion version = factory.createFinalVersion(scmState().onReleaseTag().hasUncomittedChanges().build(), Version.valueOf('1.0.0')) diff --git a/src/test/groovy/pl/allegro/tech/build/axion/release/domain/properties/VersionPropertiesBuilder.groovy b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/properties/VersionPropertiesBuilder.groovy index e9fb85c7..ab8d0365 100644 --- a/src/test/groovy/pl/allegro/tech/build/axion/release/domain/properties/VersionPropertiesBuilder.groovy +++ b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/properties/VersionPropertiesBuilder.groovy @@ -19,6 +19,8 @@ class VersionPropertiesBuilder { private boolean sanitizeVersion = true + private boolean shouldForceIncrement = false; + private VersionPropertiesBuilder() { } @@ -35,7 +37,8 @@ class VersionPropertiesBuilder { PredefinedVersionIncrementer.versionIncrementerFor('incrementPatch'), sanitizeVersion, useHighestVersion, - monorepoProperties + monorepoProperties, + shouldForceIncrement ) } @@ -58,7 +61,7 @@ class VersionPropertiesBuilder { this.useHighestVersion = true return this } - + VersionPropertiesBuilder supportMonorepos(MonorepoProperties monorepoProperties) { this.monorepoProperties = monorepoProperties return this @@ -73,4 +76,9 @@ class VersionPropertiesBuilder { this.sanitizeVersion = false return this } + + VersionPropertiesBuilder forceIncrementVersion() { + this.shouldForceIncrement = true + return this + } } diff --git a/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/VersionPropertiesFactoryTest.groovy b/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/VersionPropertiesFactoryTest.groovy index 981a3170..596cdc2e 100644 --- a/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/VersionPropertiesFactoryTest.groovy +++ b/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/VersionPropertiesFactoryTest.groovy @@ -27,7 +27,7 @@ class VersionPropertiesFactoryTest extends Specification { versionConfig.sanitizeVersion = false when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master', false) then: rules.versionIncrementer() == new Version.Builder('1.2.3').build() @@ -36,7 +36,7 @@ class VersionPropertiesFactoryTest extends Specification { def "should return forceVersion false when project has no 'release.version' property"() { when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master', false) then: !rules.forceVersion() @@ -47,7 +47,7 @@ class VersionPropertiesFactoryTest extends Specification { project.extensions.extraProperties.set('release.version', '') when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master', false) then: !rules.forceVersion() @@ -58,7 +58,7 @@ class VersionPropertiesFactoryTest extends Specification { project.extensions.extraProperties.set('release.version', 'version') when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master', false) then: rules.forceVersion() @@ -70,7 +70,7 @@ class VersionPropertiesFactoryTest extends Specification { project.extensions.extraProperties.set('release.version', ' version ') when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master', false) then: rules.forceVersion() @@ -82,7 +82,7 @@ class VersionPropertiesFactoryTest extends Specification { project.extensions.extraProperties.set('release.forceVersion', 'version') when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master', false) then: rules.forceVersion() @@ -94,7 +94,7 @@ class VersionPropertiesFactoryTest extends Specification { versionConfig.ignoreUncommittedChanges = false when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master', false) then: !rules.ignoreUncommittedChanges @@ -106,7 +106,7 @@ class VersionPropertiesFactoryTest extends Specification { project.extensions.extraProperties.set('release.ignoreUncommittedChanges', true) when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master', false) then: rules.ignoreUncommittedChanges @@ -120,7 +120,7 @@ class VersionPropertiesFactoryTest extends Specification { ] when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master', false) then: rules.versionCreator(null, null) == 'default' @@ -134,7 +134,7 @@ class VersionPropertiesFactoryTest extends Specification { ] when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'someBranch') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'someBranch', false) then: rules.versionCreator(null, null) == 'someBranch' @@ -148,7 +148,7 @@ class VersionPropertiesFactoryTest extends Specification { ] when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'someBranch') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'someBranch', false) then: rules.versionCreator('1.0.0', scmPosition('someBranch')) == '1.0.0-someBranch' @@ -164,7 +164,7 @@ class VersionPropertiesFactoryTest extends Specification { project.extensions.extraProperties.set('release.versionCreator', 'simple') when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'someBranch') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'someBranch', false) then: rules.versionCreator('1.0.0', scmPosition('someBranch')) == '1.0.0' @@ -178,7 +178,7 @@ class VersionPropertiesFactoryTest extends Specification { ] when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master', false) then: rules.versionIncrementer( @@ -194,7 +194,7 @@ class VersionPropertiesFactoryTest extends Specification { ] when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'someBranch') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'someBranch', false) then: rules.versionIncrementer( @@ -210,7 +210,7 @@ class VersionPropertiesFactoryTest extends Specification { ] when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'someBranch') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'someBranch', false) then: rules.versionIncrementer( @@ -226,7 +226,7 @@ class VersionPropertiesFactoryTest extends Specification { ] when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'someBranch') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'someBranch', false) then: rules.versionIncrementer( @@ -240,7 +240,7 @@ class VersionPropertiesFactoryTest extends Specification { project.extensions.extraProperties.set('release.versionIncrementer', 'incrementMajor') when: - VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'someBranch') + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'someBranch', false) then: rules.versionIncrementer( @@ -248,4 +248,17 @@ class VersionPropertiesFactoryTest extends Specification { ) == Version.forIntegers(2) } + + def "should set shouldForceIncrement if set"() { + given: + versionConfig.versionIncrementer = { new Version.Builder('1.2.3').build() } + versionConfig.sanitizeVersion = false + + when: + VersionProperties rules = VersionPropertiesFactory.create(project, versionConfig, 'master', true) + + then: + rules.isShouldForceIncrementVersion() + } + } diff --git a/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/git/GitRepositoryTest.groovy b/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/git/GitRepositoryTest.groovy index 5eec09a7..6469a4e8 100644 --- a/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/git/GitRepositoryTest.groovy +++ b/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/git/GitRepositoryTest.groovy @@ -59,6 +59,33 @@ class GitRepositoryTest extends Specification { rawRepository.tag.list()*.fullName == ['refs/tags/release-1'] } + def "should create new tag on non-head commit"() { + given: + String initialCommitRevision = rawRepository.log(maxCommits: 1)*.id.first() + println initialCommitRevision + + when: + repository.commit(['*'], "2nd commit") + repository.tagOnCommit(initialCommitRevision, 'release-1') + + then: + rawRepository.describe(commit: initialCommitRevision, tags: true) == 'release-1' + } + + def "should not throw exception if attempting to recreate tag on non-head commit"() { + given: + String initialCommitRevision = rawRepository.log(maxCommits: 1)*.id.first() + println initialCommitRevision + + when: + repository.commit(['*'], "2nd commit") + repository.tagOnCommit(initialCommitRevision, 'release-1') + repository.tagOnCommit(initialCommitRevision, 'release-1') + + then: + rawRepository.describe(commit: initialCommitRevision, tags: true) == 'release-1' + } + def "should create tag when on HEAD even if it already exists on the same commit"() { given: repository.tag('release-1')