diff --git a/CHANGELOG.md b/CHANGELOG.md index 43cb74b..b9ee5ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.3.6] +### Fixed +- Fix BREAKING CHANGE regex to also accept BREAKING-CHANGE and BREAKING_CHANGE due to an incompatibility between the Conventional commit specification and the git trailers convention: +git trailers only detect keywords which do not contain space character (e.g. BREAKING-CHANGE is detected but not BREAKING CHANGE) whereas Conventional commit specification specified BREAKING CHANGE with a space. + +## [2.3.5] +### Fixed +- Fix large process outputs could lead to a timeout in the command runner due to the reading buffer running full and blocking the process. + +## [2.3.4] +### Fixed +- Remove debug statements. + +## [2.3.3] +### Fixed +- Fix dirty working tree in the conventional commits incrementer. + ## [2.3.2] ### Fixed - Fix conventional commits incrementer for BREAKING CHANGES marked by a ! after the scope. diff --git a/README.md b/README.md index 1c2db38..70cbbe0 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Gradle 2.1 and higher ``` plugins { - id("io.wusa.semver-git-plugin").version("2.3.2") + id("io.wusa.semver-git-plugin").version("2.3.6") } ``` @@ -25,7 +25,7 @@ buildscript { } } dependencies { - classpath 'io.wusa:semver-git-plugin:2.3.0' + classpath 'io.wusa:semver-git-plugin:2.3.6' } } diff --git a/build.gradle.kts b/build.gradle.kts index c019ccc..efa6671 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } group = "io.wusa" -version = "2.3.2" +version = "2.3.6" dependencies { implementation(kotlin("stdlib-jdk8")) diff --git a/src/main/kotlin/io/wusa/GitCommandRunner.kt b/src/main/kotlin/io/wusa/GitCommandRunner.kt index 9d68b7d..eb19b48 100644 --- a/src/main/kotlin/io/wusa/GitCommandRunner.kt +++ b/src/main/kotlin/io/wusa/GitCommandRunner.kt @@ -8,8 +8,9 @@ class GitCommandRunner { companion object { fun execute(projectDir: File, args: Array): String { val process = startGitProcess(args, projectDir) + val output = readProcessOutput(process) waitForGitProcess(process) - if (processFinishedWithoutErrors(process)) return readProcessOutput(process) + if (processFinishedWithoutErrors(process)) return output throw GitException("Executing git command failed with " + process.exitValue()) } diff --git a/src/main/kotlin/io/wusa/GitService.kt b/src/main/kotlin/io/wusa/GitService.kt index 731de61..50ebeb2 100644 --- a/src/main/kotlin/io/wusa/GitService.kt +++ b/src/main/kotlin/io/wusa/GitService.kt @@ -41,9 +41,9 @@ class GitService { @Throws(NoLastTagFoundException::class) fun lastTag(project : Project, tagPrefix : String = "", tagType : TagType = TagType.ANNOTATED): String { - var cmdArgs = arrayOf("describe", "--dirty", "--abbrev=7", "--match", "$tagPrefix*") + var cmdArgs = arrayOf("describe", "--abbrev=7", "--match", "$tagPrefix*") if (tagType == TagType.LIGHTWEIGHT){ - cmdArgs = arrayOf("describe", "--tags", "--dirty", "--abbrev=7", "--match", "$tagPrefix*") + cmdArgs = arrayOf("describe", "--tags", "--abbrev=7", "--match", "$tagPrefix*") } return try { GitCommandRunner.execute(project.projectDir, cmdArgs) @@ -69,9 +69,9 @@ class GitService { } fun getCommitsSinceLastTag(project: Project, tagPrefix : String = "", tagType : TagType = TagType.ANNOTATED): List { - var cmdArgs = arrayOf("describe", "--dirty", "--abbrev=0", "--match", "$tagPrefix*") + var cmdArgs = arrayOf("describe", "--abbrev=0", "--match", "$tagPrefix*") if (tagType == TagType.LIGHTWEIGHT) { - cmdArgs = arrayOf("describe", "--tags", "--dirty", "--abbrev=0", "--match", "$tagPrefix*") + cmdArgs = arrayOf("describe", "--tags", "--abbrev=0", "--match", "$tagPrefix*") } return try { val lastTag = GitCommandRunner.execute(project.projectDir, cmdArgs) @@ -81,8 +81,9 @@ class GitService { } } - private fun isGitDifferent(project: Project) = - GitCommandRunner.execute(project.projectDir, arrayOf("diff", "--stat")) != "" + private fun isGitDifferent(project: Project): Boolean { + return GitCommandRunner.execute(project.projectDir, arrayOf("status", "-s")).isNotBlank() + } private fun getCurrentCommit(project: Project): String { return try { diff --git a/src/main/kotlin/io/wusa/VersionService.kt b/src/main/kotlin/io/wusa/VersionService.kt index a9271aa..264e5a2 100644 --- a/src/main/kotlin/io/wusa/VersionService.kt +++ b/src/main/kotlin/io/wusa/VersionService.kt @@ -5,6 +5,7 @@ import io.wusa.extension.SemverGitPluginExtension import io.wusa.incrementer.VersionIncrementer import org.gradle.api.GradleException import org.gradle.api.Project +import org.gradle.internal.impldep.org.eclipse.jgit.api.Git class VersionService(private var project: Project) { private val semverGitPluginExtension: SemverGitPluginExtension = project.extensions.getByType(SemverGitPluginExtension::class.java) @@ -19,6 +20,22 @@ class VersionService(private var project: Project) { throw GradleException("The current tag is not a semantic version.") } catch (ex: NoCurrentTagFoundException) { handleNoCurrentTagFound(versionFactory, project) + } catch (ex: DirtyWorkingTreeException) { + handleDirtyWorkingTree(versionFactory, project) + } + } + + @Throws(GradleException::class) + private fun handleDirtyWorkingTree(versionFactory: IVersionFactory, project: Project): Version { + return try { + val lastVersion = getLastVersion(versionFactory) + incrementVersion(lastVersion, project) + } catch (ex: NoValidSemverTagFoundException) { + throw GradleException(ex.localizedMessage) + } catch (ex: NoIncrementerFoundException) { + throw GradleException(ex.localizedMessage) + } catch (ex: NoLastTagFoundException) { + buildInitialVersion(versionFactory) } } @@ -71,6 +88,10 @@ class VersionService(private var project: Project) { if ( !curTag.startsWith(tagPrefix)) { throw NoCurrentTagFoundException("$curTag doesn't match $tagPrefix") } + val isDirty = GitService.isDirty(project) + if (isDirty) { + throw DirtyWorkingTreeException("The current working tree is dirty.") + } return versionFactory.createFromString(curTag.substring(tagPrefix.length)) } diff --git a/src/main/kotlin/io/wusa/exception/DirtyWorkingTreeException.kt b/src/main/kotlin/io/wusa/exception/DirtyWorkingTreeException.kt new file mode 100644 index 0000000..c798a2b --- /dev/null +++ b/src/main/kotlin/io/wusa/exception/DirtyWorkingTreeException.kt @@ -0,0 +1,7 @@ +package io.wusa.exception + +class DirtyWorkingTreeException: Exception { + constructor(message: String, ex: Throwable?): super(message, ex) {} + constructor(message: String): super(message) {} + constructor(ex: Throwable): super(ex) {} +} \ No newline at end of file diff --git a/src/main/kotlin/io/wusa/formatter/SemanticVersionFormatter.kt b/src/main/kotlin/io/wusa/formatter/SemanticVersionFormatter.kt index 0b33d15..21318a7 100644 --- a/src/main/kotlin/io/wusa/formatter/SemanticVersionFormatter.kt +++ b/src/main/kotlin/io/wusa/formatter/SemanticVersionFormatter.kt @@ -2,7 +2,6 @@ package io.wusa.formatter import io.wusa.Info import io.wusa.RegexResolver -import io.wusa.Suffix import io.wusa.extension.Branches import io.wusa.extension.SemverGitPluginExtension import org.gradle.api.Transformer @@ -12,12 +11,12 @@ class SemanticVersionFormatter { fun format(info: Info, branches: Branches, snapshotSuffix: String, dirtyMarker: String): String { if (!hasFirstCommit(info)) return appendSuffix(buildVersionString(info), snapshotSuffix) - if (hasTag(info)) { + if (hasTag(info) && !isDirty(info)) { return formatVersionWithTag(info) } - val formattedVersion = formatVersionWithoutTag(branches, info, dirtyMarker) - if (!hasTag(info)) { + val formattedVersion = formatVersion(branches, info, dirtyMarker) + if (!hasTag(info) || hasTag(info) && isDirty(info)) { return appendSuffix(formattedVersion, snapshotSuffix) } return formattedVersion @@ -48,19 +47,25 @@ class SemanticVersionFormatter { private fun buildVersionString(info: Info) = "${info.version.major}.${info.version.minor}.${info.version.patch}" - private fun formatVersionWithoutTag(branches: Branches, info: Info, dirtyMarker: String): String { + private fun formatVersion(branches: Branches, info: Info, dirtyMarker: String): String { val regexFormatterPair = RegexResolver.findMatchingRegex(branches, info.branch.name) var formattedVersion = transform(SemverGitPluginExtension.DEFAULT_FORMATTER, info) - formattedVersion = appendDirtyMarker(formattedVersion, info.version.suffix, dirtyMarker) + if (isDirty(info)) { + formattedVersion = appendDirtyMarker(formattedVersion, dirtyMarker) + } regexFormatterPair?.let { formattedVersion = transform(regexFormatterPair.formatter, info) - formattedVersion = appendDirtyMarker(formattedVersion, info.version.suffix, dirtyMarker) + if (isDirty(info)) { + formattedVersion = appendDirtyMarker(formattedVersion, dirtyMarker) + } } return formattedVersion } private fun hasTag(info: Info) = info.version.suffix == null + private fun isDirty(info: Info) = info.dirty + private fun hasVersionBuildInformation(info: Info) = info.version.build != "" private fun hasVersionPrerelease(info: Info) = info.version.prerelease != "" @@ -79,12 +84,9 @@ class SemanticVersionFormatter { return true } - private fun appendDirtyMarker(version: String, suffix: Suffix?, dirtyMarker: String): String { - if (suffix != null && suffix.dirty) { - if (dirtyMarker != "") { - return "$version-$dirtyMarker" - } - return version + private fun appendDirtyMarker(version: String, dirtyMarker: String): String { + if (dirtyMarker != "") { + return "$version-$dirtyMarker" } return version } diff --git a/src/main/kotlin/io/wusa/incrementer/ConventionalCommitsIncrementer.kt b/src/main/kotlin/io/wusa/incrementer/ConventionalCommitsIncrementer.kt index b8418f2..d5a7f84 100644 --- a/src/main/kotlin/io/wusa/incrementer/ConventionalCommitsIncrementer.kt +++ b/src/main/kotlin/io/wusa/incrementer/ConventionalCommitsIncrementer.kt @@ -16,7 +16,9 @@ class ConventionalCommitsIncrementer: IIncrementer { val optionalScope = "(\\(.*?\\))?" val feat = "^feat$optionalScope" val fix = "^fix$optionalScope" - val breakingChange = "\\bBREAKING CHANGE\\b:" + val breakingChange = "\\bBREAKING[ _-]CHANGE\\b:" + + if (semverGitPluginExtension.info.dirty) patch = 1 listOfCommits.forEach { when { diff --git a/src/test/kotlin/io/wusa/FunctionalBaseTest.kt b/src/test/kotlin/io/wusa/FunctionalBaseTest.kt index cf9ea57..0e13335 100644 --- a/src/test/kotlin/io/wusa/FunctionalBaseTest.kt +++ b/src/test/kotlin/io/wusa/FunctionalBaseTest.kt @@ -6,7 +6,10 @@ import java.io.File abstract class FunctionalBaseTest { fun initializeGitWithBranch(directory: File, tag: String = "0.1.0", branch: String = "develop"): Git { + val gitIgnore = File(directory, ".gitignore") + gitIgnore.writeText(".gradle") val git = Git.init().setDirectory(directory).call() + git.add().addFilepattern(".").call() val commit = git.commit().setMessage("").call() git.checkout().setCreateBranch(true).setName(branch).call() git.tag().setName(tag).setObjectId(commit).call() @@ -14,21 +17,30 @@ abstract class FunctionalBaseTest { } fun initializeGitWithoutBranchAnnotated(directory: File, tag: String = "0.1.0"): Git { + val gitIgnore = File(directory, ".gitignore") + gitIgnore.writeText(".gradle") val git = Git.init().setDirectory(directory).call() + git.add().addFilepattern(".").call() val commit = git.commit().setMessage("").call() git.tag().setName(tag).setMessage(tag).setAnnotated(true).setObjectId(commit).call() return git } fun initializeGitWithoutBranchLightweight(directory: File, tag: String = "0.1.0"): Git { + val gitIgnore = File(directory, ".gitignore") + gitIgnore.writeText(".gradle") val git = Git.init().setDirectory(directory).call() + git.add().addFilepattern(".").call() val commit = git.commit().setMessage("").call() git.tag().setName(tag).setObjectId(commit).setAnnotated(false).call() return git } fun initializeGitWithoutBranchAndWithoutTag(directory: File): Git { + val gitIgnore = File(directory, ".gitignore") + gitIgnore.writeText(".gradle") val git = Git.init().setDirectory(directory).call() + git.add().addFilepattern(".").call() git.commit().setMessage("").call() return git } diff --git a/src/test/kotlin/io/wusa/SemverGitPluginKotlinFunctionalTest.kt b/src/test/kotlin/io/wusa/SemverGitPluginKotlinFunctionalTest.kt index 848b3cf..c2b2189 100644 --- a/src/test/kotlin/io/wusa/SemverGitPluginKotlinFunctionalTest.kt +++ b/src/test/kotlin/io/wusa/SemverGitPluginKotlinFunctionalTest.kt @@ -649,4 +649,124 @@ class SemverGitPluginKotlinFunctionalTest : FunctionalBaseTest() { println(result.output) Assertions.assertTrue(result.output.contains("Version: 2.1.0-SNAPSHOT")) } + + @Test + fun `issue-59 increment minor by one with a dirty working tree`() { + val testProjectDirectory = createTempDir() + val buildFile = File(testProjectDirectory, "build.gradle.kts") + buildFile.writeText(""" + import io.wusa.Info + import io.wusa.TagType + + plugins { + id("io.wusa.semver-git-plugin") + } + + semver { + tagPrefix = "" + tagType = TagType.ANNOTATED + branches { + branch { + regex = ".+" + incrementer = "CONVENTIONAL_COMMITS_INCREMENTER" + formatter = Transformer{ info:Info -> "${'$'}{info.version.major}.${'$'}{info.version.minor}.${'$'}{info.version.patch}" } + } + } + } + """) + val git = initializeGitWithoutBranchAnnotated(testProjectDirectory, "2.0.42") + git.commit().setMessage("feat: another commit").call() + git.commit().setMessage("feat: added semver plugin incrementer parameter").call() + git.commit().setMessage("feat: added semver plugin incrementer parameter").call() + git.commit().setMessage("feat: added semver plugin incrementer parameter").call() + val dirty = File(testProjectDirectory, "dirty.file") + dirty.writeText("dirty") + git.add().addFilepattern(".").call() + + val result = gradleRunner + .withProjectDir(testProjectDirectory) + .withArguments("showInfo") + .withPluginClasspath() + .build() + println(result.output) + Assertions.assertTrue(result.output.contains("Version: 2.1.0-dirty-SNAPSHOT")) + } + + @Test + fun `issue-59 dirty working tree with no commits`() { + val testProjectDirectory = createTempDir() + val buildFile = File(testProjectDirectory, "build.gradle.kts") + buildFile.writeText(""" + import io.wusa.Info + import io.wusa.TagType + + plugins { + id("io.wusa.semver-git-plugin") + } + + semver { + tagPrefix = "" + tagType = TagType.ANNOTATED + branches { + branch { + regex = ".+" + incrementer = "CONVENTIONAL_COMMITS_INCREMENTER" + formatter = Transformer{ info:Info -> "${'$'}{info.version.major}.${'$'}{info.version.minor}.${'$'}{info.version.patch}" } + } + } + } + """) + val git = initializeGitWithoutBranchAnnotated(testProjectDirectory, "2.0.42") + val dirty = File(testProjectDirectory, "dirty.file") + dirty.writeText("dirty") + git.add().addFilepattern(".").call() + + val result = gradleRunner + .withProjectDir(testProjectDirectory) + .withArguments("showInfo") + .withPluginClasspath() + .build() + println(result.output) + Assertions.assertTrue(result.output.contains("Version: 2.0.43-dirty-SNAPSHOT")) + } + + @Test + fun `issue-61 large process outputs leads to timeouts`() { + val testProjectDirectory = createTempDir() + val buildFile = File(testProjectDirectory, "build.gradle.kts") + buildFile.writeText(""" + import io.wusa.Info + import io.wusa.TagType + + plugins { + id("io.wusa.semver-git-plugin") + } + + semver { + tagPrefix = "" + tagType = TagType.ANNOTATED + branches { + branch { + regex = ".+" + incrementer = "CONVENTIONAL_COMMITS_INCREMENTER" + formatter = Transformer{ info:Info -> "${'$'}{info.version.major}.${'$'}{info.version.minor}.${'$'}{info.version.patch}" } + } + } + } + """) + val git = initializeGitWithoutBranchAnnotated(testProjectDirectory, "2.0.42") + for (x in 0..600) { + val dirty = File(testProjectDirectory, "dirty_$x.file") + dirty.writeText("dirty") + } + git.add().addFilepattern(".").call() + + val result = gradleRunner + .withProjectDir(testProjectDirectory) + .withArguments("showInfo") + .withPluginClasspath() + .build() + println(result.output) + Assertions.assertTrue(result.output.contains("Version: 2.0.43-dirty-SNAPSHOT")) + } }