From 262cb852b94602a8593b2772fa666b0134e7107a Mon Sep 17 00:00:00 2001 From: Adam Dubiel Date: Mon, 24 Nov 2014 17:51:40 +0100 Subject: [PATCH] resolving #18 - added option to create empty commit to mark release --- CHANGELOG.md | 1 + README.md | 25 ++++++- .../build/axion/release/ReleasePlugin.groovy | 3 +- .../build/axion/release/ReleaseTask.groovy | 32 +++------ ...edefinedReleaseCommitMessageCreator.groovy | 29 ++++++++ .../axion/release/domain/Releaser.groovy | 50 ++++++++++++++ .../axion/release/domain/VersionConfig.groovy | 18 +++-- .../release/domain/scm/ScmRepository.groovy | 1 + .../infrastructure/ComponentFactory.groovy | 7 ++ .../infrastructure/dry/DryRepository.groovy | 4 ++ .../infrastructure/git/GitRepository.groovy | 7 ++ .../axion/release/domain/ReleaserTest.groovy | 67 +++++++++++++++++++ 12 files changed, 209 insertions(+), 35 deletions(-) create mode 100644 src/main/groovy/pl/allegro/tech/build/axion/release/domain/PredefinedReleaseCommitMessageCreator.groovy create mode 100644 src/main/groovy/pl/allegro/tech/build/axion/release/domain/Releaser.groovy create mode 100644 src/test/groovy/pl/allegro/tech/build/axion/release/domain/ReleaserTest.groovy diff --git a/CHANGELOG.md b/CHANGELOG.md index ea3543e3..d3bc4975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ axion-release-plugin changelog ==== + * **0.9.3** (16.10.2014) * added predefined version creators * **0.9.2** (06.10.2014) diff --git a/README.md b/README.md index ebc3c9bf..967ddde3 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ axion-release-plugin Releasing versions in Gradle is very different from releasing in Maven. Maven came with [maven-release-plugin](http://maven.apache.org/maven-release/maven-release-plugin/) which did all the dirty work. Gradle has no such tool and probably doesn't need it anyway. Evolution of software craft came -to the point, when we start thinking about SCM as ultimate source of truth about project version. No longer version +to the point, when we start thinking about SCM as ultimate source of truth about project version. Version should not be hardcoded in **pom.xml** or **build.gradle**. **axion-release-plugin** embraces this philosophy. Instead of reading project version from buildfile, it is derived @@ -38,7 +38,7 @@ buildscript { mavenCentral() } dependencies { - classpath group: 'pl.allegro.tech.build', name: 'axion-release-plugin', version: '0.9.3' + classpath group: 'pl.allegro.tech.build', name: 'axion-release-plugin', version: '0.9.5' } } @@ -55,7 +55,8 @@ scmVersion { project.version = scmVersion.version ``` -**Warning** Order of definitions in `build.gradle` file does matter! First you apply plugin, then comes `scmVersion { }` closure if configuration is needed and only then you can use `scmVersion.version` to extract current version. +**Warning** Order of definitions in `build.gradle` file does matter! First you apply plugin, then comes `scmVersion { }` +closure if configuration is needed and only then you can use `scmVersion.version` to extract current version. ### Multi-project builds @@ -180,6 +181,9 @@ scmVersion { versionCreator { version, position -> /* ... */ } // creates version visible for Gradle from raw version and current position in scm versionCreator 'versionWithBrach' // use one of predefined version creators + createReleaseCommit true // should create empty commit to annotate release in commit history, false by default + releaseCommitMessage { version, position -> /* ... */ } // custom commit message if commits are created + branchVersionCreators = [ 'feature/.*': { version, position -> /* ... */ }, 'bugfix/.*': { version, position -> /* ... */ } @@ -246,6 +250,21 @@ $ ./gradlew cV release-0.1.0-feature-some_feature-SNAPSHOT ``` +#### Create commit on release + +By default **axion-release-plugin** operates on tags only and does not mess with commit history. However, in some cases it +might be useful to create additional commit to mark release. Use `createReleaseCommit` option to change this behavior. + +Default commit message is created using closure: + +```groovy +{ version, position -> + "release version: $version" +} +``` + +It can be changed by overriding `releaseCommitMessage` property with own closure. + ## Publishing released version Publishing release version is simple with **axion-release-plugin**. Since release does not increase version unless 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 96134143..af8d4e29 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 @@ -18,8 +18,7 @@ class ReleasePlugin implements Plugin { @Override void apply(Project project) { - VersionConfig config = project.extensions.create(VERSION_EXTENSION, VersionConfig, project) - config.versionService = ComponentFactory.versionService(project, config) + VersionConfig config = ComponentFactory.versionConfig(project, VERSION_EXTENSION) Task verifyReleaseTask = project.tasks.create(VERIFY_RELEASE_TASK, VerifyReleaseTask) verifyReleaseTask.group = 'Release' 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 14058f4d..4068b9b7 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 @@ -5,6 +5,7 @@ import org.gradle.api.DefaultTask import org.gradle.api.Project import org.gradle.api.tasks.TaskAction import pl.allegro.tech.build.axion.release.domain.LocalOnlyResolver +import pl.allegro.tech.build.axion.release.domain.Releaser import pl.allegro.tech.build.axion.release.domain.VersionConfig import pl.allegro.tech.build.axion.release.domain.VersionService import pl.allegro.tech.build.axion.release.domain.VersionWithPosition @@ -18,11 +19,15 @@ class ReleaseTask extends DefaultTask { private final VersionConfig versionConfig - private final ScmRepository repository + private final Releaser releaser ReleaseTask() { this.versionConfig = project.extensions.getByType(VersionConfig) - this.repository = createRepository(project, versionConfig) + this.releaser = new Releaser( + createRepository(project, versionConfig), + new LocalOnlyResolver(versionConfig, project), + logger + ) } private ScmRepository createRepository(Project project, VersionConfig versionConfig) { @@ -32,27 +37,6 @@ class ReleaseTask extends DefaultTask { @TaskAction void release() { - LocalOnlyResolver localOnlyResolver = new LocalOnlyResolver(versionConfig, project) - VersionWithPosition positionedVersion = versionConfig.getRawVersion() - Version version = positionedVersion.version - - if (version.preReleaseVersion == VersionService.SNAPSHOT) { - version = version.setPreReleaseVersion(null) - String tagName = versionConfig.tag.serialize(versionConfig.tag, version.toString()) - - project.logger.quiet("Creating tag: $tagName") - repository.tag(tagName) - - if(!localOnlyResolver.localOnly(repository.remoteAttached(versionConfig.remote))) { - project.logger.quiet("Pushing all to remote: ${versionConfig.remote}") - repository.push(versionConfig.remote) - } - else { - project.logger.quiet("Changes made to local repository only") - } - } - else { - project.logger.quiet("Working on released version ${versionConfig.version}, nothing to do here.") - } + releaser.release(versionConfig) } } diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/PredefinedReleaseCommitMessageCreator.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/PredefinedReleaseCommitMessageCreator.groovy new file mode 100644 index 00000000..a599aaa3 --- /dev/null +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/PredefinedReleaseCommitMessageCreator.groovy @@ -0,0 +1,29 @@ +package pl.allegro.tech.build.axion.release.domain + +import pl.allegro.tech.build.axion.release.domain.scm.ScmPosition + +enum PredefinedReleaseCommitMessageCreator { + + DEFAULT('default', { String version, ScmPosition position -> + return "release version: $version" + }); + + private final String type + + final Closure commitMessageCreator + + private PredefinedReleaseCommitMessageCreator(String type, Closure c) { + this.type = type + this.commitMessageCreator = c + } + + static Closure commitMessageCreatorFor(String type) { + PredefinedReleaseCommitMessageCreator creator = PredefinedReleaseCommitMessageCreator.values().find { it.type == type } + if (creator == null) { + throw new IllegalArgumentException("There is no predefined commit message creator with $type type. " + + "You can choose from: ${PredefinedReleaseCommitMessageCreator.values().collect { it.type }}"); + } + return creator.commitMessageCreator + } + +} diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/Releaser.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/Releaser.groovy new file mode 100644 index 00000000..c5792da1 --- /dev/null +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/Releaser.groovy @@ -0,0 +1,50 @@ +package pl.allegro.tech.build.axion.release.domain + +import com.github.zafarkhaja.semver.Version +import org.gradle.api.logging.Logger +import pl.allegro.tech.build.axion.release.domain.scm.ScmRepository + +class Releaser { + + private final ScmRepository repository + + private final LocalOnlyResolver localOnlyResolver + + private final Logger logger + + Releaser(ScmRepository repository, LocalOnlyResolver localOnlyResolver, Logger logger) { + this.repository = repository + this.localOnlyResolver = localOnlyResolver + this.logger = logger + } + + void release(VersionConfig versionConfig) { + VersionWithPosition positionedVersion = versionConfig.getRawVersion() + Version version = positionedVersion.version + + if (version.preReleaseVersion == VersionService.SNAPSHOT) { + version = version.setPreReleaseVersion(null) + String tagName = versionConfig.tag.serialize(versionConfig.tag, version.toString()) + + if(versionConfig.createReleaseCommit) { + logger.quiet("Creating release commit") + repository.commit(versionConfig.releaseCommitMessage(version.toString(), positionedVersion.position)) + } + + logger.quiet("Creating tag: $tagName") + repository.tag(tagName) + + if(!localOnlyResolver.localOnly(repository.remoteAttached(versionConfig.remote))) { + logger.quiet("Pushing all to remote: ${versionConfig.remote}") + repository.push(versionConfig.remote) + } + else { + logger.quiet("Changes made to local repository only") + } + } + else { + logger.quiet("Working on released version ${versionConfig.version}, nothing to do here.") + } + } + +} diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionConfig.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionConfig.groovy index 44f4c6d2..06f9597e 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionConfig.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionConfig.groovy @@ -28,18 +28,16 @@ class VersionConfig { boolean sanitizeVersion = true + boolean createReleaseCommit = false + + Closure releaseCommitMessage = PredefinedReleaseCommitMessageCreator.DEFAULT.commitMessageCreator + VersionService versionService private String resolvedVersion = null private VersionWithPosition rawVersion = null - private static Closure defaultVersionCreator() { - return { String versionFromTag, ScmPosition position -> - return versionFromTag.toString() - } - } - @Inject VersionConfig(Project project) { this.project = project @@ -57,6 +55,14 @@ class VersionConfig { this.versionCreator = PredefinedVersionCreator.versionCreatorFor(type) } + void releaseCommitMessage(Closure c) { + releaseCommitMessage = c + } + + void createReleaseCommit(boolean createReleaseCommit) { + this.createReleaseCommit = createReleaseCommit + } + void versionCreator(Closure c) { this.versionCreator = c } diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/scm/ScmRepository.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/scm/ScmRepository.groovy index a8f46f68..e2c5cf87 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/scm/ScmRepository.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/scm/ScmRepository.groovy @@ -22,4 +22,5 @@ interface ScmRepository { boolean checkAheadOfRemote() + List lastLogMessages(int messageCount) } diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/ComponentFactory.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/ComponentFactory.groovy index 046d2a77..f99b7e94 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/ComponentFactory.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/ComponentFactory.groovy @@ -22,6 +22,13 @@ class ComponentFactory { private static ScmRepository repository + static VersionConfig versionConfig(Project project, String extensionName) { + VersionConfig config = project.extensions.create(extensionName, VersionConfig, project) + config.versionService = ComponentFactory.versionService(project, config) + + return config + } + static VersionService versionService(Project project, VersionConfig versionConfig) { ScmRepository repository = scmRepository(project, versionConfig) return new VersionService(new VersionResolver(repository)) diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/dry/DryRepository.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/dry/DryRepository.groovy index 3d9f8ad0..22a79525 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/dry/DryRepository.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/dry/DryRepository.groovy @@ -73,6 +73,10 @@ class DryRepository implements ScmRepository { return aheadOfRemote } + @Override + List lastLogMessages(int messageCount) { + return delegateRepository.lastLogMessages(messageCount) + } private void log(String msg) { logger.quiet("DRY-RUN: $msg") diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/git/GitRepository.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/git/GitRepository.groovy index eba1b3b4..1aefb286 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/git/GitRepository.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/git/GitRepository.groovy @@ -199,4 +199,11 @@ class GitRepository implements ScmRepository { void checkoutBranch(String branchName) { repository.repository.jgit.checkout().setName(branchName).setCreateBranch(true).call() } + + @Override + List lastLogMessages(int messageCount) { + ensureRepositoryExists() + + return repository.log(maxCommits: messageCount)*.fullMessage + } } 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 new file mode 100644 index 00000000..d6b5c2dc --- /dev/null +++ b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/ReleaserTest.groovy @@ -0,0 +1,67 @@ +package pl.allegro.tech.build.axion.release.domain + +import org.ajoberstar.grgit.Grgit +import org.gradle.api.Project +import org.gradle.testfixtures.ProjectBuilder +import pl.allegro.tech.build.axion.release.domain.scm.ScmRepository +import pl.allegro.tech.build.axion.release.infrastructure.ComponentFactory +import spock.lang.Specification + +class ReleaserTest extends Specification { + + Project project + + ScmRepository repository + + Releaser releaser + + VersionConfig config + + def setup() { + project = ProjectBuilder.builder().build() + + Grgit.init(dir: project.rootDir) + + config = ComponentFactory.versionConfig(project, 'scmVersion') + repository = ComponentFactory.scmRepository(project, config) + + repository.commit('initial commit') + + releaser = new Releaser(repository, new LocalOnlyResolver(config, project), project.logger) + } + + def "should release new version when not on tag"() { + given: + project.extensions.extraProperties.set('release.forceVersion', '2.0.0') + + when: + releaser.release(config) + + then: + config.getVersion() == '2.0.0' + } + + def "should not release version when on tag"() { + given: + repository.tag('release-1.0.0') + + when: + releaser.release(config) + + then: + config.getVersion() == '1.0.0' + } + + def "should create release commit if configured"() { + given: + project.extensions.extraProperties.set('release.forceVersion', '3.0.0') + config.createReleaseCommit = true + + when: + releaser.release(config) + + then: + config.getVersion() == '3.0.0' + repository.lastLogMessages(1) == ['release version: 3.0.0'] + } +}