From 7202376bba5549a2ac75976f2bf4730638c02f25 Mon Sep 17 00:00:00 2001 From: john-tipper Date: Sat, 18 Jan 2020 19:48:10 +0000 Subject: [PATCH] Introduces monorepo support * First quick and dirty attempt * Introduce TaggedCommits * Refactor method name: currentPostion(path) -> positionOfLastChangeIn(path) * Wrapper class around TagsOnCommit, with factory methods * #291 Initial commit * #310 #291 Updated JGit & GrGit to exclude dirs when calculating versions. * #310 #291 Implement requested changes. --- build.gradle | 10 +- docs/configuration/basic_usage.md | 24 +- docs/configuration/overview.md | 3 + .../MultiModuleProjectIntegrationTest.groovy | 189 +++++++++++++++ .../release/domain/MonorepoConfig.groovy | 5 + .../axion/release/domain/VersionConfig.groovy | 6 + .../release/domain/VersionResolver.groovy | 39 +-- .../axion/release/domain/VersionSorter.groovy | 22 +- .../properties/VersionProperties.groovy | 4 +- .../release/domain/scm/ScmRepository.groovy | 2 + .../release/domain/scm/TagsOnCommit.groovy | 7 +- .../infrastructure/DryRepository.groovy | 5 + .../infrastructure/DummyRepository.groovy | 11 +- .../config/VersionPropertiesFactory.groovy | 8 +- .../release/infrastructure/di/Context.groovy | 11 +- .../di/GradleAwareContext.groovy | 1 + .../infrastructure/git/GitRepository.groovy | 54 ++++- .../domain/properties/MonorepoProperties.java | 19 ++ .../release/domain/scm/TaggedCommits.java | 46 ++++ .../config/MonorepoPropertiesFactory.java | 11 + .../axion/release/RepositoryBasedTest.groovy | 2 +- .../VersionResolverSubfolderTest.groovy | 224 ++++++++++++++++++ .../release/domain/VersionResolverTest.groovy | 2 +- .../release/domain/VersionSorterTest.groovy | 23 +- .../MonorepoPropertiesBuilder.groovy | 22 ++ .../VersionPropertiesBuilder.groovy | 10 +- .../MonorepoPropertiesFactoryTest.groovy | 33 +++ .../git/DryRepositoryTest.groovy | 3 +- .../git/GitRepositoryTest.groovy | 3 - 29 files changed, 733 insertions(+), 66 deletions(-) create mode 100644 src/integration/groovy/pl/allegro/tech/build/axion/release/MultiModuleProjectIntegrationTest.groovy create mode 100644 src/main/groovy/pl/allegro/tech/build/axion/release/domain/MonorepoConfig.groovy create mode 100644 src/main/java/pl/allegro/tech/build/axion/release/domain/properties/MonorepoProperties.java create mode 100644 src/main/java/pl/allegro/tech/build/axion/release/domain/scm/TaggedCommits.java create mode 100644 src/main/java/pl/allegro/tech/build/axion/release/infrastructure/config/MonorepoPropertiesFactory.java create mode 100644 src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionResolverSubfolderTest.groovy create mode 100644 src/test/groovy/pl/allegro/tech/build/axion/release/domain/properties/MonorepoPropertiesBuilder.groovy create mode 100644 src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/MonorepoPropertiesFactoryTest.groovy diff --git a/build.gradle b/build.gradle index 04aa5da7..c886af42 100644 --- a/build.gradle +++ b/build.gradle @@ -36,15 +36,21 @@ sourceCompatibility = '1.7' repositories { mavenCentral() + jcenter() } project.ext.versions = [ - jgit: '4.11.0.201803080745-r', + jgit: '5.6.0.201912101111-r', jsch: '0.1.54', jschAgent: '0.0.9' ] sourceSets { + main { + java { srcDirs = [] } // no source dirs for the java compiler + groovy { srcDirs = ["src/main/java", "src/main/groovy"] } // compile everything in src/ with groovy + } + integration { java.srcDir project.file('src/integration/java') groovy.srcDir project.file('src/integration/groovy') @@ -91,7 +97,7 @@ dependencies { compile group: 'com.github.zafarkhaja', name: 'java-semver', version: '0.9.0' - testCompile (group: 'org.ajoberstar', name: 'grgit', version: '1.7.2') { + testCompile (group: 'org.ajoberstar.grgit', name: 'grgit-core', version: '4.0.1') { exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit.ui' exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit' } diff --git a/docs/configuration/basic_usage.md b/docs/configuration/basic_usage.md index 520ae9ca..673578ac 100644 --- a/docs/configuration/basic_usage.md +++ b/docs/configuration/basic_usage.md @@ -77,7 +77,7 @@ allprojects { } ``` -### Multi-module with multiple versions +### Multi-module project with multiple versions Sometimes it might be desirable to release each module (or just some modules) of multi-module project separately. If so, please make sure @@ -117,6 +117,28 @@ my-service my-service-client ``` +Use the `projectDirs` configuration parameter within a `monorepos` block to identify submodules +that should be excluded from consideration when calculating whether to increment +the version of the parent project. Typically, you would do this in the top level +project, assuming that submodules are named after the directory they appear in: + +``` +scmVersion { + monorepos { + projectDirs = project.subprojects.collect({p -> p.name}) + } +} +``` + +Version calculation rules: +1. Changes to files within a submodule increment that submodule's version only. +2. Changes to a submodule do not cause a change to the parent project's version if +the parent is set to ignore that submodule, via `foldersToExclude`. +3. Changes to files in the parent project but which are not in a submodule identified via +`foldersToExclude` will cause the parent project's version to increment but not the +versions of any submodules. If this is desired then consider wiring the `createRelease` or +`release` tasks of the submodules to be dependencies of the tasks of the same name in the parent. + ## Releasing ``` diff --git a/docs/configuration/overview.md b/docs/configuration/overview.md index 2eda5d2c..9306453e 100644 --- a/docs/configuration/overview.md +++ b/docs/configuration/overview.md @@ -28,6 +28,9 @@ All `axion-release-plugin` configuration options: // doc: Version / Sanitization sanitizeVersion = true // should created version be sanitized, true by default + // doc: Basic usage / Basic configuration + foldersToExclude = ['submodule1', 'submodule2'] // ignore changes in these subdirs when calculating changes to parent + tag { // doc: Version / Parsing prefix = 'tag-prefix' // prefix to be used, 'release' by default branchPrefix = [ // set different prefix per branch diff --git a/src/integration/groovy/pl/allegro/tech/build/axion/release/MultiModuleProjectIntegrationTest.groovy b/src/integration/groovy/pl/allegro/tech/build/axion/release/MultiModuleProjectIntegrationTest.groovy new file mode 100644 index 00000000..6c35d884 --- /dev/null +++ b/src/integration/groovy/pl/allegro/tech/build/axion/release/MultiModuleProjectIntegrationTest.groovy @@ -0,0 +1,189 @@ +package pl.allegro.tech.build.axion.release + +import org.gradle.testkit.runner.TaskOutcome + +import java.util.stream.Collectors + +class MultiModuleProjectIntegrationTest 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 and 3.0.0 for the 2 child projects. + */ + void initialProjectConfiguration() { + List submodules = ["project1", "project2"] + buildFile(''' + scmVersion { + monorepos { + projectDirs = project.subprojects.collect({p -> p.name}) + } + } + ''' + ) + 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}"], "commit submodule ${module}") + // 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 + + } + + def "change to submodule should not change sibling project version"() { + given: + initialProjectConfiguration() + File dummy = new File(getSubmoduleDir("project1"), "dummy.txt") + dummy.createNewFile() + repository.commit(["project1"], "commit submodule project1") + runGradle(":project1:release", "-Prelease.version=4.0.0", '-Prelease.localOnly', '-Prelease.disableChecks') + + when: + def result = runGradle(':project1:currentVersion') + def match = result.output =~ /(?m)^.*Project version: (.*)$/ + + then: + match[0][1] == '4.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 + + } + + def "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:release", "-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 "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(":release", "-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 + + } +} diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/MonorepoConfig.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/MonorepoConfig.groovy new file mode 100644 index 00000000..fa90d018 --- /dev/null +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/MonorepoConfig.groovy @@ -0,0 +1,5 @@ +package pl.allegro.tech.build.axion.release.domain + +class MonorepoConfig { + public List projectDirs = [] +} 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 ca1257a6..38f29362 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 @@ -49,6 +49,8 @@ class VersionConfig { HooksConfig hooks = new HooksConfig() + MonorepoConfig monorepoConfig = new MonorepoConfig() + private Context context private VersionService.DecoratedVersion resolvedVersion = null @@ -165,4 +167,8 @@ class VersionConfig { this.context = GradleAwareContext.create(project, this) } } + + void monorepos(Closure c) { + project.configure(monorepoConfig, c) + } } diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionResolver.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionResolver.groovy index 77412b4d..d9d6c6aa 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionResolver.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionResolver.groovy @@ -6,7 +6,7 @@ import pl.allegro.tech.build.axion.release.domain.properties.TagProperties import pl.allegro.tech.build.axion.release.domain.properties.VersionProperties import pl.allegro.tech.build.axion.release.domain.scm.ScmPosition import pl.allegro.tech.build.axion.release.domain.scm.ScmRepository -import pl.allegro.tech.build.axion.release.domain.scm.TagsOnCommit +import pl.allegro.tech.build.axion.release.domain.scm.TaggedCommits import java.util.regex.Pattern @@ -24,16 +24,23 @@ class VersionResolver { private final ScmRepository repository private final VersionSorter sorter - - VersionResolver(ScmRepository repository) { + private ScmPosition latestChangePosition + /** + * This is the path of the project relative to the Git root. + * If this path is not empty then it means that the project is running as a submodule of a parent project. + */ + private String projectRootRelativePath + + VersionResolver(ScmRepository repository, String projectRootRelativePath) { this.repository = repository + this.projectRootRelativePath = projectRootRelativePath this.sorter = new VersionSorter() } VersionContext resolveVersion(VersionProperties versionRules, TagProperties tagProperties, NextVersionProperties nextVersionProperties) { - ScmPosition position = repository.currentPosition() + latestChangePosition = repository.positionOfLastChangeIn(projectRootRelativePath, versionRules.monorepoProperties.dirsToExclude) - VersionFactory versionFactory = new VersionFactory(versionRules, tagProperties, nextVersionProperties, position) + VersionFactory versionFactory = new VersionFactory(versionRules, tagProperties, nextVersionProperties, latestChangePosition) Map versions if (versionFactory.versionProperties.useHighestVersion) { @@ -51,7 +58,7 @@ class VersionResolver { Map finalVersion = versionFactory.createFinalVersion(scmState, versions.current) - return new VersionContext(finalVersion.version, finalVersion.snapshot, versions.previous, position) + return new VersionContext(finalVersion.version, finalVersion.snapshot, versions.previous, latestChangePosition) } private Map readVersions(VersionFactory versionFactory, @@ -64,15 +71,13 @@ class VersionResolver { boolean forceSnapshot = versionProperties.forceSnapshot Map currentVersionInfo, previousVersionInfo - TagsOnCommit latestTags = repository.latestTags(releaseTagPattern) - currentVersionInfo = versionFromTaggedCommits([latestTags], false, nextVersionTagPattern, + TaggedCommits latestTaggedCommit = TaggedCommits.fromLatestCommit(repository, releaseTagPattern, latestChangePosition) + currentVersionInfo = versionFromTaggedCommits(latestTaggedCommit, false, nextVersionTagPattern, versionFactory, forceSnapshot) + boolean onCommitWithLatestChange = currentVersionInfo.commit == latestChangePosition.revision - TagsOnCommit previousTags = latestTags - while (previousTags.hasOnlyMatching(nextVersionTagPattern)) { - previousTags = repository.latestTags(releaseTagPattern, previousTags.commitId) - } - previousVersionInfo = versionFromTaggedCommits([previousTags], true, nextVersionTagPattern, + TaggedCommits previousTaggedCommit = TaggedCommits.fromLatestCommitBeforeNextVersion(repository, releaseTagPattern, nextVersionTagPattern, latestChangePosition) + previousVersionInfo = versionFromTaggedCommits(previousTaggedCommit, true, nextVersionTagPattern, versionFactory, forceSnapshot) Version currentVersion = currentVersionInfo.version @@ -80,7 +85,7 @@ class VersionResolver { return [ current : currentVersion, previous : previousVersion, - onReleaseTag : currentVersionInfo.isHead && !currentVersionInfo.isNextVersion, + onReleaseTag : onCommitWithLatestChange && !currentVersionInfo.isNextVersion, onNextVersionTag: currentVersionInfo.isNextVersion, noTagsFound : currentVersionInfo.noTagsFound ] @@ -96,7 +101,7 @@ class VersionResolver { boolean forceSnapshot = versionProperties.forceSnapshot Map currentVersionInfo, previousVersionInfo - List allTaggedCommits = repository.taggedCommits(releaseTagPattern) + TaggedCommits allTaggedCommits = TaggedCommits.fromAllCommits(repository, releaseTagPattern, latestChangePosition) currentVersionInfo = versionFromTaggedCommits(allTaggedCommits, false, nextVersionTagPattern, versionFactory, forceSnapshot) @@ -108,13 +113,13 @@ class VersionResolver { return [ current : currentVersion, previous : previousVersion, - onReleaseTag : currentVersionInfo.isHead && !currentVersionInfo.isNextVersion, + onReleaseTag : (currentVersionInfo.commit == latestChangePosition.revision) && !currentVersionInfo.isNextVersion, onNextVersionTag: currentVersionInfo.isNextVersion, noTagsFound : currentVersionInfo.noTagsFound ] } - private Map versionFromTaggedCommits(List taggedCommits, + private Map versionFromTaggedCommits(TaggedCommits taggedCommits, boolean ignoreNextVersionTags, Pattern nextVersionTagPattern, VersionFactory versionFactory, diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionSorter.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionSorter.groovy index 25e3d14f..5c162340 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionSorter.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionSorter.groovy @@ -1,12 +1,13 @@ package pl.allegro.tech.build.axion.release.domain import com.github.zafarkhaja.semver.Version +import pl.allegro.tech.build.axion.release.domain.scm.TaggedCommits import pl.allegro.tech.build.axion.release.domain.scm.TagsOnCommit import java.util.regex.Pattern /** - * Contains logic of sorting out which version should be used when there are mutliple + * Contains logic of sorting out which version should be used when there are multiple * versions available. * * Precedence: @@ -20,24 +21,24 @@ import java.util.regex.Pattern */ class VersionSorter { - Map pickTaggedCommit(List taggedCommits, - boolean ignoreNextVersionTags, - boolean forceSnapshot, - Pattern nextVersionTagPattern, - VersionFactory versionFactory) { + Map pickTaggedCommit(TaggedCommits taggedCommits, + boolean ignoreNextVersionTags, + boolean forceSnapshot, + Pattern nextVersionTagPattern, + VersionFactory versionFactory) { Set versions = [] Map isVersionNextVersion = [:] Map versionToCommit = new LinkedHashMap<>() - for (TagsOnCommit tagsEntry : taggedCommits) { + for (TagsOnCommit tagsEntry : taggedCommits.getCommits()) { List tags = tagsEntry.tags - // next version should be igored when tag is on head + // next version should be ignored when tag is on head // and there are other, normal tags on it // because when on single commit on head - normal ones have precedence // however, we should take into account next version // in case of forced snapshot - boolean ignoreNextVersionOnHead = tagsEntry.isHead && + boolean ignoreNextVersionOnHead = taggedCommits.isLatestCommit(tagsEntry.commitId) && !tagsEntry.hasOnlyMatching(nextVersionTagPattern) && !forceSnapshot @@ -75,8 +76,7 @@ class VersionSorter { version : version, isNextVersion: isVersionNextVersion.containsKey(version) && isVersionNextVersion[version], noTagsFound : versions.isEmpty(), - commit : versionCommit?.commitId, - isHead : versionCommit?.isHead + commit : versionCommit?.commitId ] } diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/properties/VersionProperties.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/properties/VersionProperties.groovy index 8c268ed1..c3ff99b3 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/properties/VersionProperties.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/properties/VersionProperties.groovy @@ -17,9 +17,11 @@ class VersionProperties { final Closure versionIncrementer final boolean sanitizeVersion - + final boolean useHighestVersion + final MonorepoProperties monorepoProperties + boolean forceVersion() { return forcedVersion != null } 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 25577b62..60dace96 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 @@ -18,6 +18,8 @@ interface ScmRepository { ScmPosition currentPosition() + ScmPosition positionOfLastChangeIn(String path, List excludeSubFolders) + TagsOnCommit latestTags(Pattern pattern) TagsOnCommit latestTags(Pattern pattern, String sinceCommit) diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/scm/TagsOnCommit.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/scm/TagsOnCommit.groovy index 53285777..9d20899f 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/scm/TagsOnCommit.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/scm/TagsOnCommit.groovy @@ -8,16 +8,13 @@ class TagsOnCommit { final List tags = new ArrayList<>() - final boolean isHead - - TagsOnCommit(String commitId, List tags, boolean isHead) { + TagsOnCommit(String commitId, List tags) { this.commitId = commitId this.tags.addAll(tags) - this.isHead = isHead } static TagsOnCommit empty() { - return new TagsOnCommit(null, [], false) + return new TagsOnCommit(null, []) } boolean hasOnlyMatching(Pattern pattern) { 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 57a9f8e8..736c5fba 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 @@ -57,6 +57,11 @@ class DryRepository implements ScmRepository { return delegateRepository.currentPosition() } + @Override + ScmPosition positionOfLastChangeIn(String path, List excludeSubFolders) { + return delegateRepository.positionOfLastChangeIn(path) + } + @Override TagsOnCommit latestTags(Pattern pattern) { TagsOnCommit tags = delegateRepository.latestTags(pattern) 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 1d9d1188..555fa4bd 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 @@ -57,22 +57,27 @@ class DummyRepository implements ScmRepository { return new ScmPosition('', '', 'master') } + @Override + ScmPosition positionOfLastChangeIn(String path, List excludeSubFolders) { + return new ScmPosition('', '', 'master') + } + @Override TagsOnCommit latestTags(Pattern pattern) { logger.quiet("Could not resolve current position on uninitialized repository, returning default") - return new TagsOnCommit(null, [], false) + return new TagsOnCommit(null, []) } @Override TagsOnCommit latestTags(Pattern pattern, String sinceCommit) { logger.quiet("Could not resolve current position on uninitialized repository, returning default") - return new TagsOnCommit(null, [], false) + return new TagsOnCommit(null, []) } @Override List taggedCommits(Pattern pattern) { logger.quiet("Could not resolve current position on uninitialized repository, returning default") - return [new TagsOnCommit(null, [], false)] + return [new TagsOnCommit(null, [])] } 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 70b6a1b6..6de3a37d 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 @@ -3,7 +3,6 @@ package pl.allegro.tech.build.axion.release.infrastructure.config import org.gradle.api.Project import pl.allegro.tech.build.axion.release.domain.PredefinedVersionCreator import pl.allegro.tech.build.axion.release.domain.PredefinedVersionIncrementer -import pl.allegro.tech.build.axion.release.domain.ProjectVersion import pl.allegro.tech.build.axion.release.domain.VersionConfig import pl.allegro.tech.build.axion.release.domain.properties.VersionProperties @@ -18,7 +17,7 @@ class VersionPropertiesFactory { private static final String IGNORE_UNCOMMITTED_CHANGES_PROPERTY = 'release.ignoreUncommittedChanges' private static final String FORCE_SNAPSHOT_PROPERTY = 'release.forceSnapshot' - + private static final String USE_HIGHEST_VERSION_PROPERTY = 'release.useHighestVersion' private static final String VERSION_INCREMENTER_PROPERTY = 'release.versionIncrementer' @@ -33,7 +32,7 @@ class VersionPropertiesFactory { boolean ignoreUncommittedChanges = project.hasProperty(IGNORE_UNCOMMITTED_CHANGES_PROPERTY) ?: config.ignoreUncommittedChanges boolean forceSnapshot = project.hasProperty(FORCE_SNAPSHOT_PROPERTY) - + boolean useHighestVersion = project.hasProperty(USE_HIGHEST_VERSION_PROPERTY) ?: config.useHighestVersion return new VersionProperties( @@ -43,7 +42,8 @@ class VersionPropertiesFactory { versionCreator: findVersionCreator(project, config, currentBranch), versionIncrementer: findVersionIncrementer(project, config, currentBranch), sanitizeVersion: config.sanitizeVersion, - useHighestVersion: useHighestVersion + useHighestVersion: useHighestVersion, + monorepoProperties: MonorepoPropertiesFactory.create(project, config.monorepoConfig, currentBranch) ) } 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 856e1d6f..0b6694f2 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,15 +23,14 @@ class Context { private final LocalOnlyResolver localOnlyResolver - public Context(Properties rules, ScmRepository scmRepository, ScmProperties scmProperties, LocalOnlyResolver localOnlyResolver) { + public Context(Properties rules, ScmRepository scmRepository, ScmProperties scmProperties, File projectRoot, LocalOnlyResolver localOnlyResolver) { this.rules = rules this.scmRepository = scmRepository this.scmProperties = scmProperties this.localOnlyResolver = localOnlyResolver instances[ScmRepository] = scmRepository - instances[VersionService] = new VersionService(new VersionResolver(scmRepository)) - + instances[VersionService] = new VersionService(new VersionResolver(scmRepository, scmProperties.directory.toPath().relativize(projectRoot.toPath()).toString())) } private T get(Class clazz) { @@ -60,9 +59,9 @@ class Context { Releaser releaser() { return new Releaser( - versionService(), - scmService(), - new ReleaseHooksRunner(versionService(), scmService()) + versionService(), + scmService(), + new ReleaseHooksRunner(versionService(), scmService()) ) } 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 0bb97d0c..68420e7e 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 @@ -18,6 +18,7 @@ class GradleAwareContext { RulesFactory.create(project, versionConfig, scmRepository.currentPosition()), scmRepository, scmProperties, + project.projectDir, LocalOnlyResolverFactory.create(project, versionConfig) ) } 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 9bf9d89a..1e4399c2 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 @@ -176,6 +176,58 @@ class GitRepository implements ScmRepository { .call() } + @Override + ScmPosition positionOfLastChangeIn(String path, List excludeSubFolders) { + assertPathFormat(path) + RevCommit lastCommit + + // if the path is empty ('') then it means we are at the root of the Git directory + // in which case, we should exclude changes that occurred in subdirectory projects when deciding on + // which is the latest change that is relevant to the root project + if (path.isEmpty()) { + LogCommand logCommand = jgitRepository.log().setMaxCount(1) + for (String excludedPath : excludeSubFolders) { + assertPathFormat(excludedPath) + logCommand.excludePath(excludedPath) + } + lastCommit = logCommand.call()[0] + } + else { + assertPathExists(path) + lastCommit = jgitRepository.log().setMaxCount(1).addPath(path).call()[0] + } + + ScmPosition currentPosition = currentPosition() + + if (lastCommit == null) { + return currentPosition + } + + String revision = lastCommit.name + if (revision == currentPosition.revision) { + return currentPosition + } else { + return new ScmPosition( + revision, + revision[0..(7 - 1)], + currentPosition.branch + ) + } + } + + private void assertPathFormat(String path) { + if (path.contains('\\')) { + throw new ScmException("Only slashes are supported in path ('${path}')") + } + } + + private void assertPathExists(String path) { + File subpath = new File(repositoryDir, path) + if (!subpath.exists()) { + throw new ScmException("Path '${path}' does not exist in repository '${repositoryDir.getAbsolutePath()}'.") + } + } + ScmPosition currentPosition() { String revision = '' String shortRevision = '' @@ -253,7 +305,7 @@ class GitRepository implements ScmRepository { for (currentCommit = walk.next(); currentCommit != null; currentCommit = walk.next()) { currentTagsList = allTags[currentCommit.id.name] if (currentTagsList) { - TagsOnCommit taggedCommit = new TagsOnCommit(currentCommit.id.name(), currentTagsList, Objects.equals(currentCommit.id, headId)) + TagsOnCommit taggedCommit = new TagsOnCommit(currentCommit.id.name(), currentTagsList) taggedCommits.add(taggedCommit) if (stopOnFirstTag) { break diff --git a/src/main/java/pl/allegro/tech/build/axion/release/domain/properties/MonorepoProperties.java b/src/main/java/pl/allegro/tech/build/axion/release/domain/properties/MonorepoProperties.java new file mode 100644 index 00000000..4fbcd2b0 --- /dev/null +++ b/src/main/java/pl/allegro/tech/build/axion/release/domain/properties/MonorepoProperties.java @@ -0,0 +1,19 @@ +package pl.allegro.tech.build.axion.release.domain.properties; + +import groovy.transform.Immutable; + +import java.util.Collections; +import java.util.List; + +@Immutable +public class MonorepoProperties { + public final List dirsToExclude; + + public MonorepoProperties() { + this.dirsToExclude = Collections.emptyList(); + } + + public MonorepoProperties(List dirsToExclude) { + this.dirsToExclude = Collections.unmodifiableList(dirsToExclude); + } +} diff --git a/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/TaggedCommits.java b/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/TaggedCommits.java new file mode 100644 index 00000000..972359f5 --- /dev/null +++ b/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/TaggedCommits.java @@ -0,0 +1,46 @@ +package pl.allegro.tech.build.axion.release.domain.scm; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +public class TaggedCommits { + + private List commits; + private String latestCommitRevision; + + private TaggedCommits(ScmPosition latestTagPosition, List commits) { + this.commits = commits; + this.latestCommitRevision = latestTagPosition.getRevision(); + } + + public static TaggedCommits fromListOfCommits(ScmPosition latestTagPosition, List taggedCommits) { + return new TaggedCommits(latestTagPosition, taggedCommits); + } + + public static TaggedCommits fromLatestCommit(ScmRepository repository, Pattern tagPattern, ScmPosition latestTagPosition) { + TagsOnCommit latestTags = repository.latestTags(tagPattern); + return new TaggedCommits(latestTagPosition, Arrays.asList(latestTags)); + } + + public static TaggedCommits fromAllCommits(ScmRepository repository, Pattern tagPattern, ScmPosition latestTagPosition) { + List taggedCommits = repository.taggedCommits(tagPattern); + return new TaggedCommits(latestTagPosition, taggedCommits); + } + + public static TaggedCommits fromLatestCommitBeforeNextVersion(ScmRepository repository, Pattern releaseTagPattern, Pattern nextVersionTagPattern, ScmPosition latestTagPosition) { + TagsOnCommit previousTags = repository.latestTags(releaseTagPattern); + while (previousTags.hasOnlyMatching(nextVersionTagPattern)) { + previousTags = repository.latestTags(releaseTagPattern, previousTags.getCommitId()); + } + return new TaggedCommits(latestTagPosition, Arrays.asList(previousTags)); + } + + public List getCommits() { + return commits; + } + + public boolean isLatestCommit(String revision) { + return latestCommitRevision == null ? false : latestCommitRevision.equals(revision); + } +} diff --git a/src/main/java/pl/allegro/tech/build/axion/release/infrastructure/config/MonorepoPropertiesFactory.java b/src/main/java/pl/allegro/tech/build/axion/release/infrastructure/config/MonorepoPropertiesFactory.java new file mode 100644 index 00000000..5bad0d8b --- /dev/null +++ b/src/main/java/pl/allegro/tech/build/axion/release/infrastructure/config/MonorepoPropertiesFactory.java @@ -0,0 +1,11 @@ +package pl.allegro.tech.build.axion.release.infrastructure.config; + +import org.gradle.api.Project; +import pl.allegro.tech.build.axion.release.domain.MonorepoConfig; +import pl.allegro.tech.build.axion.release.domain.properties.MonorepoProperties; + +public class MonorepoPropertiesFactory { + public static MonorepoProperties create(Project project, MonorepoConfig config, String currentBranch) { + return new MonorepoProperties(config.projectDirs); + } +} diff --git a/src/test/groovy/pl/allegro/tech/build/axion/release/RepositoryBasedTest.groovy b/src/test/groovy/pl/allegro/tech/build/axion/release/RepositoryBasedTest.groovy index 568bf0bd..1c384b62 100644 --- a/src/test/groovy/pl/allegro/tech/build/axion/release/RepositoryBasedTest.groovy +++ b/src/test/groovy/pl/allegro/tech/build/axion/release/RepositoryBasedTest.groovy @@ -4,7 +4,6 @@ import org.ajoberstar.grgit.Grgit import org.junit.Rule import org.junit.rules.TemporaryFolder import pl.allegro.tech.build.axion.release.domain.LocalOnlyResolver -import pl.allegro.tech.build.axion.release.domain.properties.Properties import pl.allegro.tech.build.axion.release.domain.properties.PropertiesBuilder import pl.allegro.tech.build.axion.release.domain.scm.ScmProperties import pl.allegro.tech.build.axion.release.domain.scm.ScmRepository @@ -36,6 +35,7 @@ class RepositoryBasedTest extends Specification { PropertiesBuilder.properties().build(), scmRepository, scmProperties, + directory, new LocalOnlyResolver(true) ) diff --git a/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionResolverSubfolderTest.groovy b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionResolverSubfolderTest.groovy new file mode 100644 index 00000000..8f8bdcb8 --- /dev/null +++ b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionResolverSubfolderTest.groovy @@ -0,0 +1,224 @@ +package pl.allegro.tech.build.axion.release.domain + +import pl.allegro.tech.build.axion.release.RepositoryBasedTest +import pl.allegro.tech.build.axion.release.domain.properties.* +import pl.allegro.tech.build.axion.release.infrastructure.di.Context +import spock.lang.Shared + +import static pl.allegro.tech.build.axion.release.domain.properties.NextVersionPropertiesBuilder.nextVersionProperties +import static pl.allegro.tech.build.axion.release.domain.properties.TagPropertiesBuilder.tagProperties +import static pl.allegro.tech.build.axion.release.domain.scm.ScmPropertiesBuilder.scmProperties + +class VersionResolverSubfolderTest extends RepositoryBasedTest { + + VersionResolver resolver + + TagProperties tagRules = tagProperties().build() + + NextVersionProperties nextVersionRules = nextVersionProperties().build() + + @Shared + MonorepoProperties defaultMonorepoProperties = MonorepoPropertiesBuilder.monorepoProperties() + .build() + + VersionProperties defaultMonorepoVersionRules = VersionPropertiesBuilder.versionProperties() + .supportMonorepos(defaultMonorepoProperties) + .build() + + String projectRootSubfolder + + def setup() { + this.projectRootSubfolder = "gradleProjectRoot" + resolver = new VersionResolver(repository, projectRootSubfolder) + } + + def "should return default previous and current version when no tag in repository"() { + given: + createAndCommitFileInSubfolder(projectRootSubfolder, 'foo') + configureContextWithVersionRules(defaultMonorepoVersionRules) + + when: + VersionContext version = resolver.resolveVersion(defaultMonorepoVersionRules, tagRules, nextVersionRules) + + then: + version.previousVersion.toString() == '0.1.0' + version.version.toString() == '0.1.0' + version.snapshot + + } + + def "should return same previous and current version when no change in subfolder since release tag"() { + given: + createAndCommitFileInSubfolder(projectRootSubfolder, 'foo') + repository.tag('release-1.1.0') + repository.commit(['*'], 'Commit without change in subfolder') + configureContextWithVersionRules(defaultMonorepoVersionRules) + + when: + VersionContext version = resolver.resolveVersion(defaultMonorepoVersionRules, tagRules, nextVersionRules) + + then: + version.previousVersion.toString() == '1.1.0' + version.version.toString() == '1.1.0' + !version.snapshot + } + + def "should pick tag with highest version when multiple tags on last commit with changes in subfolder"() { + given: + createAndCommitFileInSubfolder(projectRootSubfolder, 'foo') + repository.tag('release-1.0.0') + repository.tag('release-1.1.0') + repository.tag('release-1.2.0') + repository.commit(['*'], 'Commit without change in subfolder') + configureContextWithVersionRules(defaultMonorepoVersionRules) + + when: + VersionContext version = resolver.resolveVersion(defaultMonorepoVersionRules, tagRules, nextVersionRules) + + then: + version.previousVersion.toString() == '1.2.0' + version.version.toString() == '1.2.0' + !version.snapshot + } + + def "should pick tag with highest version when multiple release and non-release tags on last commit with changes in subfolder"() { + given: + createAndCommitFileInSubfolder(projectRootSubfolder, 'foo') + repository.tag('release-1.0.0') + repository.tag('release-1.1.0') + repository.tag('release-1.1.5-alpha') + repository.tag('release-1.2.0') + repository.tag('release-1.4.0-alpha') + repository.commit(['*'], 'Commit without change in subfolder') + configureContextWithVersionRules(defaultMonorepoVersionRules) + + when: + VersionContext version = resolver.resolveVersion(defaultMonorepoVersionRules, tagRules, nextVersionRules) + + then: + version.previousVersion.toString() == '1.2.0' + version.version.toString() == '1.2.0' + !version.snapshot + } + + def "should prefer snapshot of nextVersion when both on latest relevant commit and forceSnapshot is enabled"() { + + given: "there is releaseTag and nextVersionTag on current commit" + createAndCommitFileInSubfolder(projectRootSubfolder, 'foo') + repository.tag('release-1.0.0') + repository.tag('release-1.1.0-alpha') + repository.commit(['*'], 'Commit without change in subfolder') + + VersionProperties versionRules = VersionPropertiesBuilder.versionProperties() + .supportMonorepos(defaultMonorepoProperties) + .forceSnapshot() + .build() + configureContextWithVersionRules(versionRules) + + when: "resolving version with property 'release.forceSnapshot'" + VersionContext version = resolver.resolveVersion(versionRules, tagRules, nextVersionRules) + + then: "the resolved version should be snapshot towards the next version" + version.previousVersion.toString() == '1.0.0' + version.version.toString() == '1.1.0' + version.snapshot + } + + def "should return unmodified previous and incremented current version when changes in subfolder since tag"(VersionProperties versionRules) { + given: + createAndCommitFileInSubfolder(projectRootSubfolder, 'foo') + repository.tag('release-1.1.0') + createAndCommitFileInSubfolder(projectRootSubfolder, 'bar') + + when: + configureContextWithVersionRules(versionRules) + VersionContext version = resolver.resolveVersion(versionRules, tagRules, nextVersionRules) + + then: + version.previousVersion.toString() == '1.1.0' + version.version.toString() == '1.1.1' + version.snapshot + + where: + versionRules << [ + VersionPropertiesBuilder.versionProperties().supportMonorepos(defaultMonorepoProperties).build(), + VersionPropertiesBuilder.versionProperties().supportMonorepos(defaultMonorepoProperties).forceSnapshot().build() + ] + } + + def "should return the highest version from the tagged versions, even if subfolder not changed in that commit"(VersionProperties versionRules) { + given: + createAndCommitFileInSubfolder(projectRootSubfolder, 'foo') + repository.tag('release-1.0.0') + repository.commit(['*'], 'Commit without change in subfolder') + repository.tag('release-1.5.0') + createAndCommitFileInSubfolder(projectRootSubfolder, 'bar') + repository.tag('release-1.2.0') + createAndCommitFileInSubfolder(projectRootSubfolder, 'bar2') + repository.tag('release-1.3.0') + + when: + configureContextWithVersionRules(versionRules) + VersionContext version = resolver.resolveVersion(versionRules, tagRules, nextVersionRules) + println "Version Resolved: $version" + + then: + version.previousVersion.toString() == '1.5.0' + version.version.toString() == '1.5.1' + version.snapshot + + where: + versionRules << [ + VersionPropertiesBuilder.versionProperties().supportMonorepos(defaultMonorepoProperties).useHighestVersion().build(), + VersionPropertiesBuilder.versionProperties().supportMonorepos(defaultMonorepoProperties).useHighestVersion().forceSnapshot().build() + ] + } + + def "should return the highest version from the tagged versions, even if subfolder not changed in that commit, when not on release"(VersionProperties versionRules) { + given: + createAndCommitFileInSubfolder(projectRootSubfolder, 'foo') + repository.tag('release-1.0.0') + repository.commit(['*'], 'Commit without change in subfolder') + repository.tag('release-1.5.0') + createAndCommitFileInSubfolder(projectRootSubfolder, 'bar') + repository.tag('release-1.2.0') + createAndCommitFileInSubfolder(projectRootSubfolder, 'bar2') + repository.tag('release-1.3.0') + repository.commit(['*'], 'Commit without change in subfolder') + + when: + configureContextWithVersionRules(versionRules) + VersionContext version = resolver.resolveVersion(versionRules, tagRules, nextVersionRules) + + then: + version.previousVersion.toString() == '1.5.0' + version.version.toString() == '1.5.1' + version.snapshot + + where: + versionRules << [ + VersionPropertiesBuilder.versionProperties().supportMonorepos(defaultMonorepoProperties).useHighestVersion().build(), + VersionPropertiesBuilder.versionProperties().supportMonorepos(defaultMonorepoProperties).useHighestVersion().forceSnapshot().build() + ] + } + + private void configureContextWithVersionRules(VersionProperties versionRules) { + context = new Context( + PropertiesBuilder.properties().withVersionRules(versionRules).build(), + context.repository(), + scmProperties(directory).build(), + directory, + new LocalOnlyResolver(true) + ) + + repository = context.repository() + } + + private void createAndCommitFileInSubfolder(String path, String filename) { + def subfolder = new File(directory, path) + def dummyFile = new File(directory, "${path}/${filename}") + subfolder.mkdirs() + dummyFile.createNewFile() + repository.commit(['.'], "create file ${path}/${filename}") + } +} diff --git a/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionResolverTest.groovy b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionResolverTest.groovy index 6ed2dd97..3096346d 100644 --- a/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionResolverTest.groovy +++ b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionResolverTest.groovy @@ -20,7 +20,7 @@ class VersionResolverTest extends RepositoryBasedTest { VersionProperties defaultVersionRules = versionProperties().build() def setup() { - resolver = new VersionResolver(repository) + resolver = new VersionResolver(repository, "") } def "should return default previous and current version when no tag in repository"() { diff --git a/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionSorterTest.groovy b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionSorterTest.groovy index 57e63c5b..a53683d7 100644 --- a/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionSorterTest.groovy +++ b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/VersionSorterTest.groovy @@ -3,7 +3,9 @@ package pl.allegro.tech.build.axion.release.domain import pl.allegro.tech.build.axion.release.domain.properties.NextVersionPropertiesBuilder import pl.allegro.tech.build.axion.release.domain.properties.TagPropertiesBuilder import pl.allegro.tech.build.axion.release.domain.properties.VersionPropertiesBuilder +import pl.allegro.tech.build.axion.release.domain.scm.ScmPosition import pl.allegro.tech.build.axion.release.domain.scm.ScmPositionBuilder +import pl.allegro.tech.build.axion.release.domain.scm.TaggedCommits import pl.allegro.tech.build.axion.release.domain.scm.TagsOnCommit import spock.lang.Specification @@ -29,7 +31,8 @@ class VersionSorterTest extends Specification { def "should return highest version of all when not on head"() { when: def versionData = sorter.pickTaggedCommit( - [new TagsOnCommit('a', ['release-1.0.0'], false), new TagsOnCommit('b', ['release-2.0.0'], false)], + TaggedCommits.fromListOfCommits(new ScmPosition("b", "b", "master"), + [new TagsOnCommit('a', ['release-1.0.0']), new TagsOnCommit('b', ['release-2.0.0'])]), false, false, ~/.*-alpha$/, @@ -43,7 +46,8 @@ class VersionSorterTest extends Specification { def "should return highest version of all when not on head and versions on single commit"() { when: def versionData = sorter.pickTaggedCommit( - [new TagsOnCommit('a', ['release-1.0.0', 'release-2.0.0'], false)], + TaggedCommits.fromListOfCommits(new ScmPosition("a", "a", "master"), + [new TagsOnCommit('a', ['release-1.0.0', 'release-2.0.0'])]), false, false, ~/.*-alpha$/, @@ -58,7 +62,8 @@ class VersionSorterTest extends Specification { def "should return highest version of all when alpha is on head"() { when: def versionData = sorter.pickTaggedCommit( - [new TagsOnCommit('a', ['release-1.0.0'], false), new TagsOnCommit('b', ['release-2.0.0-alpha'], true)], + TaggedCommits.fromListOfCommits(new ScmPosition("b", "b", "master"), + [new TagsOnCommit('a', ['release-1.0.0']), new TagsOnCommit('b', ['release-2.0.0-alpha'])]), false, false, ~/.*-alpha$/, @@ -72,7 +77,8 @@ class VersionSorterTest extends Specification { def "should prefer normal version to alpha version when on head and versions on single commit"() { when: def versionData = sorter.pickTaggedCommit( - [new TagsOnCommit('a', ['release-1.0.0', 'release-2.0.0-alpha'], true)], + TaggedCommits.fromListOfCommits(new ScmPosition("a", "a", "master"), + [new TagsOnCommit('a', ['release-1.0.0', 'release-2.0.0-alpha'])]), false, false, ~/.*-alpha$/, @@ -86,7 +92,8 @@ class VersionSorterTest extends Specification { def "should prefer alpha version to normal version when on head, versions on single commit and snapshot is forced"() { when: def versionData = sorter.pickTaggedCommit( - [new TagsOnCommit('a', ['release-1.0.0', 'release-2.0.0-alpha'], true)], + TaggedCommits.fromListOfCommits(new ScmPosition("a", "a", "master"), + [new TagsOnCommit('a', ['release-1.0.0', 'release-2.0.0-alpha'])]), false, true, ~/.*-alpha$/, @@ -100,7 +107,8 @@ class VersionSorterTest extends Specification { def "should ignore any nextVersion commits when asked"() { when: def versionData = sorter.pickTaggedCommit( - [new TagsOnCommit('a', ['release-1.0.0'], false), new TagsOnCommit('a', ['release-2.0.0-alpha'], false)], + TaggedCommits.fromListOfCommits(new ScmPosition("a", "a", "master"), + [new TagsOnCommit('a', ['release-1.0.0']), new TagsOnCommit('a', ['release-2.0.0-alpha'])]), true, false, ~/.*-alpha$/, @@ -114,7 +122,8 @@ class VersionSorterTest extends Specification { def "should ignore any nextVersion commits when asked and versions on single commit"() { when: def versionData = sorter.pickTaggedCommit( - [new TagsOnCommit('a', ['release-1.0.0', 'release-2.0.0-alpha'], false)], + TaggedCommits.fromListOfCommits(new ScmPosition("a", "a", "master"), + [new TagsOnCommit('a', ['release-1.0.0', 'release-2.0.0-alpha'])]), true, false, ~/.*-alpha$/, diff --git a/src/test/groovy/pl/allegro/tech/build/axion/release/domain/properties/MonorepoPropertiesBuilder.groovy b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/properties/MonorepoPropertiesBuilder.groovy new file mode 100644 index 00000000..eb45ac37 --- /dev/null +++ b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/properties/MonorepoPropertiesBuilder.groovy @@ -0,0 +1,22 @@ +package pl.allegro.tech.build.axion.release.domain.properties + +class MonorepoPropertiesBuilder { + + private List dirsToExclude = [] + + private MonorepoPropertiesBuilder() { + } + + static MonorepoPropertiesBuilder monorepoProperties() { + return new MonorepoPropertiesBuilder() + } + + MonorepoProperties build() { + return new MonorepoProperties(dirsToExclude) + } + + MonorepoPropertiesBuilder excludeDirs(List dirsToExclude) { + this.dirsToExclude = dirsToExclude + return this + } +} 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 ef27154f..2c89f9c5 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 @@ -13,6 +13,8 @@ class VersionPropertiesBuilder { private boolean useHighestVersion = false + private MonorepoProperties monorepoProperties = new MonorepoProperties() + private VersionPropertiesBuilder() { } @@ -28,7 +30,8 @@ class VersionPropertiesBuilder { versionCreator: PredefinedVersionCreator.SIMPLE.versionCreator, versionIncrementer: PredefinedVersionIncrementer.versionIncrementerFor('incrementPatch'), sanitizeVersion: true, - useHighestVersion: useHighestVersion) + useHighestVersion: useHighestVersion, + monorepoProperties: monorepoProperties) } VersionPropertiesBuilder forceVersion(String version) { @@ -50,4 +53,9 @@ class VersionPropertiesBuilder { this.useHighestVersion = true return this } + + VersionPropertiesBuilder supportMonorepos(MonorepoProperties monorepoProperties) { + this.monorepoProperties = monorepoProperties + return this + } } diff --git a/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/MonorepoPropertiesFactoryTest.groovy b/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/MonorepoPropertiesFactoryTest.groovy new file mode 100644 index 00000000..01441946 --- /dev/null +++ b/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/MonorepoPropertiesFactoryTest.groovy @@ -0,0 +1,33 @@ +package pl.allegro.tech.build.axion.release.infrastructure.config + +import org.gradle.api.Project +import pl.allegro.tech.build.axion.release.domain.MonorepoConfig +import pl.allegro.tech.build.axion.release.domain.properties.MonorepoProperties +import spock.lang.Specification + +import static org.gradle.testfixtures.ProjectBuilder.builder + +class MonorepoPropertiesFactoryTest extends Specification { + + private Project project + + private MonorepoConfig monorepoConfig + + def setup() { + project = builder().build() + monorepoConfig = new MonorepoConfig() + } + + def "should copy properties from MonorepoConfig object"() { + given: + monorepoConfig.projectDirs = ["foo", "bar"] + + when: + MonorepoProperties rules = MonorepoPropertiesFactory.create(project, monorepoConfig, 'master') + + then: + rules.dirsToExclude.size() == 2 + rules.dirsToExclude.contains("foo") + rules.dirsToExclude.contains("bar") + } +} diff --git a/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/git/DryRepositoryTest.groovy b/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/git/DryRepositoryTest.groovy index 34931d79..595ac4ad 100644 --- a/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/git/DryRepositoryTest.groovy +++ b/src/test/groovy/pl/allegro/tech/build/axion/release/infrastructure/git/DryRepositoryTest.groovy @@ -1,7 +1,6 @@ package pl.allegro.tech.build.axion.release.infrastructure.git import pl.allegro.tech.build.axion.release.domain.scm.ScmIdentity -import pl.allegro.tech.build.axion.release.domain.scm.ScmPosition import pl.allegro.tech.build.axion.release.domain.scm.ScmPushOptions import pl.allegro.tech.build.axion.release.domain.scm.ScmRepository import pl.allegro.tech.build.axion.release.domain.scm.TagsOnCommit @@ -40,7 +39,7 @@ class DryRepositoryTest extends Specification { def "should return latest tags from real scm"() { given: - TagsOnCommit expected = new TagsOnCommit('commit-id', [], false) + TagsOnCommit expected = new TagsOnCommit('commit-id', []) scm.latestTags(_) >> expected when: 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 8b29e75a..ec9887d8 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 @@ -109,7 +109,6 @@ class GitRepositoryTest extends Specification { then: tags.tags == ['release-1'] - !tags.isHead } def "should return no tags when no commit in repository"() { @@ -121,7 +120,6 @@ class GitRepositoryTest extends Specification { then: tags.tags == [] - !tags.isHead } def "should indicate that position is on tag when latest commit is tagged"() { @@ -133,7 +131,6 @@ class GitRepositoryTest extends Specification { then: tags.tags == ['release-1'] - tags.isHead } def "should track back to older tag when commit was made after checking out older version"() {