diff --git a/src/main/groovy/wooga/gradle/version/internal/release/semver/SemVerStrategy.groovy b/src/main/groovy/wooga/gradle/version/internal/release/semver/SemVerStrategy.groovy index 9cbb43d..a62db2c 100644 --- a/src/main/groovy/wooga/gradle/version/internal/release/semver/SemVerStrategy.groovy +++ b/src/main/groovy/wooga/gradle/version/internal/release/semver/SemVerStrategy.groovy @@ -17,28 +17,21 @@ package wooga.gradle.version.internal.release.semver import groovy.transform.Immutable import groovy.transform.PackageScope - -import wooga.gradle.version.ReleaseStage -import wooga.gradle.version.internal.release.base.ReleaseVersion -import wooga.gradle.version.internal.release.base.DefaultVersionStrategy import org.ajoberstar.grgit.Grgit - +import org.ajoberstar.grgit.Status import org.gradle.api.GradleException -import org.gradle.api.Project - import org.slf4j.Logger import org.slf4j.LoggerFactory -import wooga.gradle.version.VersionPluginExtension +import wooga.gradle.version.ReleaseStage +import wooga.gradle.version.internal.release.base.DefaultVersionStrategy +import wooga.gradle.version.internal.release.base.ReleaseVersion import javax.annotation.Nullable - /** * Strategy to infer versions that comply with Semantic Versioning. - * @see PartialSemVerStrategy - * @see SemVerStrategyState - * @see Wiki Doc + * @see PartialSemVerStrategy* @see SemVerStrategyState* @see Wiki Doc */ -@Immutable(copyWith=true, knownImmutableClasses=[PartialSemVerStrategy]) +@Immutable(copyWith = true, knownImmutableClasses = [PartialSemVerStrategy]) final class SemVerStrategy implements DefaultVersionStrategy { private static final Logger logger = LoggerFactory.getLogger(SemVerStrategy) @@ -101,12 +94,16 @@ final class SemVerStrategy implements DefaultVersionStrategy { if (stage != null && !stages.contains(stage)) { logger.info('Skipping {} default strategy because stage ({}) is not one of: {}', name, stage, stages) return false - } else if (!allowDirtyRepo && !grgit.status().clean) { + } + // TODO: Refactor branch statement with below? + def status = grgit.status() + if (!allowDirtyRepo && !status.clean) { logger.info('Skipping {} default strategy because repo is dirty.', name) + logger.info(composeRepositoryStatus(status)) return false } else { - String status = grgit.status().clean ? 'clean' : 'dirty' - logger.info('Using {} default strategy because repo is {} and no stage defined', name, status) + String statusString = status.clean ? 'clean' : 'dirty' + logger.info('Using {} default strategy because repo is {} and no stage defined', name, statusString) return true } } @@ -123,16 +120,39 @@ final class SemVerStrategy implements DefaultVersionStrategy { if (stage == null || !stages.contains(stage)) { logger.info('Skipping {} strategy because stage ({}) is not one of: {}', name, stage, stages) return false - } else if (!allowDirtyRepo && !grgit.status().clean) { - logger.info('Skipping {} strategy because repo is dirty.', name) + } + def status = grgit.status() + if (!allowDirtyRepo && !status.clean) { + logger.info('Skipping {} strategy because repo is dirty...', name) + logger.info(composeRepositoryStatus(status)) return false } else { - String status = grgit.status().clean ? 'clean' : 'dirty' - logger.info('Using {} strategy because repo is {} and stage ({}) is one of: {}', name, status, stage, stages) + String statusString = status.clean ? 'clean' : 'dirty' + logger.info('Using {} strategy because repo is {} and stage ({}) is one of: {}', name, statusString, stage, stages) return true } } + /** + * Composes a string detailing the current repository staged/unstaged files + */ + static String composeRepositoryStatus(Status status) { + StringBuilder str = new StringBuilder() + + Closure printChangeSet = { label, changeSet -> + str.append("\n> ${label}\n") + str.append(changeSet.added.collect({ "[ADDED] ${it}" }).join("\n")) + str.append(changeSet.modified.collect({ "[MODIFIED] ${it}" }).join("\n")) + str.append(changeSet.removed.collect({ "[REMOVED] ${it}" }).join("\n")) + } + + str.append("Repository Status:") + printChangeSet("Staged", status.staged) + printChangeSet("Unstaged", status.unstaged) + + str.toString() + } + /** * Infers the version to use for this build. Uses the normal, pre-release, and build metadata * strategies in order to infer the version. If the {@code release.stage} is not set, uses the @@ -150,14 +170,14 @@ final class SemVerStrategy implements DefaultVersionStrategy { ReleaseVersion doInfer(SemVerStrategyState semVerState) { logger.info('Beginning version inference using {} strategy and input scope ({}) and stage ({})', name, semVerState.scopeFromProp, semVerState.stageFromProp) def finalSemverState = StrategyUtil.all( - normalStrategy, preReleaseStrategy, buildMetadataStrategy).infer(semVerState) + normalStrategy, preReleaseStrategy, buildMetadataStrategy).infer(semVerState) def version = finalSemverState.toVersion() def nearestVersion = finalSemverState.nearestVersion if (enforcePrecedence && version < nearestVersion.any) { throw new GradleException("Inferred version (${version}) cannot be lower than nearest (${nearestVersion.any}). Required by selected strategy '${name}'.") } - def nearestNormal = nearestVersion.normal == NearestVersion.EMPTY? null: nearestVersion.normal + def nearestNormal = nearestVersion.normal == NearestVersion.EMPTY ? null : nearestVersion.normal return new ReleaseVersion(version.toString(), nearestNormal?.toString(), createTag) } @@ -172,16 +192,16 @@ final class SemVerStrategy implements DefaultVersionStrategy { throw new GradleException("Stage ${params.stage} is not one of ${stages} allowed for strategy ${name}.") } return new SemVerStrategyState( - scopeFromProp: params.scope, - stageFromProp: params.stage?: stages.first(), - currentHead: params.currentHead, - currentBranch: params.currentBranch, - repoDirty: params.repoDirty, - nearestVersion: params.nearestVersion, - nearestCiMarker: params.nearestCiMarker, - nearestReleaseMarker: params.nearestReleaseMarker, - releaseBranchPattern: params.releaseBranchPattern, - mainBranchPattern: params.mainBranchPattern + scopeFromProp: params.scope, + stageFromProp: params.stage ?: stages.first(), + currentHead: params.currentHead, + currentBranch: params.currentBranch, + repoDirty: params.repoDirty, + nearestVersion: params.nearestVersion, + nearestCiMarker: params.nearestCiMarker, + nearestReleaseMarker: params.nearestReleaseMarker, + releaseBranchPattern: params.releaseBranchPattern, + mainBranchPattern: params.mainBranchPattern ) } } diff --git a/src/test/groovy/wooga/gradle/version/internal/SemVerStrategySpec.groovy b/src/test/groovy/wooga/gradle/version/internal/SemVerStrategySpec.groovy new file mode 100644 index 0000000..9686c75 --- /dev/null +++ b/src/test/groovy/wooga/gradle/version/internal/SemVerStrategySpec.groovy @@ -0,0 +1,41 @@ +package wooga.gradle.version.internal + +import org.ajoberstar.grgit.Status +import spock.lang.Specification +import wooga.gradle.version.internal.release.semver.SemVerStrategy + +class SemVerStrategySpec extends Specification { + + Map changes() { + Map result = [:] + result["added"] = [] + result["modified"] = [] + result["removed"] = [] + result + } + + def "generates status string"() { + given: + def args = [:] + + def staged = changes() + staged["added"] = ["foo"] + args["staged"] = staged + + def unstaged = changes() + unstaged["removed"] = ["bar"] + args["unstaged"] = unstaged + + def conflicts = [] + args["conflicts"] = conflicts + + def status = new Status(args) + + when: + def str = SemVerStrategy.composeRepositoryStatus(status) + + then: + str.contains("[ADDED] foo") + str.contains("[REMOVED] bar") + } +}