From 44a820e64113608948d477e8dbfdb4d75581e004 Mon Sep 17 00:00:00 2001 From: JFronny Date: Mon, 25 Mar 2024 14:04:11 +0100 Subject: [PATCH] feat: rewrite commit parsing to support revert/reapply and merge commits --- .../io/gitlab/jfronny/scripts/CommitType.kt | 53 +++++++++++++------ .../io/gitlab/jfronny/scripts/JGitExt.kt | 6 ++- .../src/main/kotlin/jf.autoversion.gradle.kts | 1 + 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/convention/src/main/kotlin/io/gitlab/jfronny/scripts/CommitType.kt b/convention/src/main/kotlin/io/gitlab/jfronny/scripts/CommitType.kt index 1bafa8d..147d4f6 100644 --- a/convention/src/main/kotlin/io/gitlab/jfronny/scripts/CommitType.kt +++ b/convention/src/main/kotlin/io/gitlab/jfronny/scripts/CommitType.kt @@ -4,25 +4,48 @@ enum class CommitType { FIX, FEAT, BREAKING; companion object { - private val pattern: Regex = Regex("([a-zA-Z ]+)(?:\\([a-zA-Z0-9 -]+\\))?(!)?: .+", RegexOption.DOT_MATCHES_ALL) + private val conventionalPattern = Regex("^(\\w+)(\\(([\\w\\-.,\\s:]+)\\)?)?(!?)[\\s?]*:(.+)") + private val footerPattern = Regex("^(BREAKING[ -]CHANGE|[^ ]+)(((: )|( #))(.+))") fun from(commitMessage: String, warn: (String) -> Unit): CommitType { - val match = pattern.matchEntire(commitMessage) - if (match != null) { - val m = match.groupValues - if (m[2] == "!") return BREAKING - return when (m[1].lowercase()) { - "fix", "build", "chore", "ci", "docs", "style", "refactor", "perf", "test" -> FIX - "feat" -> FEAT - "breaking change", "breaking" -> BREAKING - else -> { - warn("Unrecognized commit type: ${m[1]}, guessing FEAT") - FEAT - } + val lines = commitMessage.trim().split("\\r?\\n") + val headerMatch = conventionalPattern.matchEntire(extractHeader(lines[0])) + if (headerMatch == null) { + warn("Could not parse commit, guessing type is FEAT: $commitMessage") + return FEAT + } + if (headerMatch.groupValues[3] == "!") return BREAKING + for (s in lines.drop(1).filterNot { it.isBlank() }) { + val footerMatch = footerPattern.matchEntire(s) + if (footerMatch != null) { + val type = footerMatch.groupValues[1].lowercase() + if (type == "breaking change" || type == "breaking-change") return BREAKING } } - warn("Could not parse commit, guessing type is FEAT: $commitMessage") - return FEAT + return when (headerMatch.groupValues[1].lowercase()) { + "fix", "build", "chore", "ci", "docs", "style", "refactor", "perf", "test" -> FIX + "feat" -> FEAT + "breaking change", "breaking" -> BREAKING + else -> { + warn("Unrecognized commit type: ${headerMatch.groupValues[1]}, guessing FEAT") + FEAT + } + } + } + + private val revertPattern = Regex("^Revert \"(.+)\"", RegexOption.IGNORE_CASE) + private val reapplyPattern = Regex("^Reapply \"(.+)\"", RegexOption.IGNORE_CASE) + + private tailrec fun extractHeader(headerLine: String): String { + if (headerLine.startsWith("Revert ")) { + val revertMatch = revertPattern.matchEntire(headerLine) + if (revertMatch != null) return extractHeader(revertMatch.groupValues[1]) + } + if (headerLine.startsWith("Reapply ")) { + val reapplyMatch = reapplyPattern.matchEntire(headerLine) + if (reapplyMatch != null) return extractHeader(reapplyMatch.groupValues[1]) + } + return headerLine } } } \ No newline at end of file diff --git a/convention/src/main/kotlin/io/gitlab/jfronny/scripts/JGitExt.kt b/convention/src/main/kotlin/io/gitlab/jfronny/scripts/JGitExt.kt index 96518ab..dc25bb5 100644 --- a/convention/src/main/kotlin/io/gitlab/jfronny/scripts/JGitExt.kt +++ b/convention/src/main/kotlin/io/gitlab/jfronny/scripts/JGitExt.kt @@ -76,7 +76,7 @@ fun RevCommit.resolve(repo: Repository): Commit = Commit( ), fullMessage, shortMessage, - parents.map { ObjectId.toString(it) } + parents?.map { ObjectId.toString(it) } ?: emptyList() ) private val String?.nullable get() = if (this == null || this == "") null else this @@ -86,4 +86,6 @@ data class Tag(val id: ObjectId, val original: RevTag?, val fullName: String, va val peeledId: ObjectId get() = commit.original.id } data class Commit(val original: RevCommit, val id: ObjectId, val abbreviatedId: String?, val committer: Person, val author: Person, val dateTime: ZonedDateTime, val fullMessage: String, val shortMessage: String, val parentIds: List) -data class Person(val original: PersonIdent, val name: String, val emailAddress: String) \ No newline at end of file +data class Person(val original: PersonIdent, val name: String, val emailAddress: String) + +private val Commit.isMerge get() = parentIds.size > 1 \ No newline at end of file diff --git a/convention/src/main/kotlin/jf.autoversion.gradle.kts b/convention/src/main/kotlin/jf.autoversion.gradle.kts index 3037377..127e00b 100644 --- a/convention/src/main/kotlin/jf.autoversion.gradle.kts +++ b/convention/src/main/kotlin/jf.autoversion.gradle.kts @@ -30,6 +30,7 @@ if (File(projectDir, ".git").exists()) { val log = git.log(tags[0].peeledId, git.repository.resolve("HEAD")).reversed() changelog += log.joinToString("\n") { "- ${it.shortMessage}" } val type = log.stream() + .filter { !it.isMerge } .map { it.fullMessage } .map { msg -> CommitType.from(msg) { logger.warn(it) } } .max { o1, o2 -> o1.compareTo(o2) }