[kotlin] Kotlin Build Tools code changes have to pass mandatory review

#KTIJ-33845

GitOrigin-RevId: cb1633213925fbf5f07cdb3509de08b3a3e0e9de
This commit is contained in:
Vladimir Dolzhenko
2025-04-17 14:42:39 +02:00
committed by intellij-monorepo-bot
parent 19fb2ccaf3
commit a89ebef6c4
5 changed files with 114 additions and 31 deletions

View File

@@ -649,7 +649,8 @@
This results in better stack traces when generated assertions throw exceptions, but might slow down the compilation."/>
<registryKey key="kotlin.commit.message.validation.enabled" defaultValue="true"
description="Enables commit messages validation for kotlin plugin"/>
<registryKey key="kotlin.build.tools.code.ownership.commit.message.enabled" defaultValue="false"
description="Enables commit messages validation for Kotlin Build Tools code ownership"/>
<codeInsight.declarativeInlayProvider
providerId="devkit.threading"
bundle="messages.DevKitBundle"

View File

@@ -4,6 +4,7 @@
<plugin id="Git4Idea"/>
</dependencies>
<extensions defaultExtensionNs="com.intellij">
<prePushHandler implementation="org.jetbrains.idea.devkit.commit.KotlinBuildToolsPrePushHandler"/>
<prePushHandler implementation="org.jetbrains.idea.devkit.commit.KotlinPluginPrePushHandler"/>
<prePushHandler implementation="org.jetbrains.idea.devkit.commit.IntelliJPrePushHandler"/>
</extensions>

View File

@@ -8,4 +8,13 @@ push.commit.message.lacks.issue.reference.body=\
push.commit.message.lacks.issue.reference.title=Message Lacks YouTrack Issue Reference
push.commit.message.lacks.issue.reference.commit=Push As Is
push.commit.message.lacks.issue.reference.edit=Edit Message(s)
push.commit.handler.idea.name=IDEA Push Handler
push.commit.handler.idea.name=IDEA Push Handler
push.commit.kotlin.build.tools.handler.name=Kotlin Build Tools Code Ownership Push Handler
push.commit.kotlin.build.tools.review.title=Kotlin Build Tools Code Ownership Warning
push.commit.kotlin.build.tools.message.lacks.issue.reference.body=\
<html>Commits listed below change the Kotlin Build Tools code.<br/><br/>\
According to the rules of the Kotlin Build Tools team, \
all commits to their codebase have to pass mandatory pre-review.\
<pre>{0}</pre> \
</html>

View File

@@ -17,15 +17,13 @@ import java.util.concurrent.atomic.AtomicReference
import kotlin.io.path.extension
import kotlin.io.path.invariantSeparatorsPathString
internal abstract class IssueIDPrePushHandler : PrePushHandler {
abstract val paths: List<String>
internal abstract class AbstractIntelliJProjectPrePushHandler : PrePushHandler {
open val paths: List<String> = listOf()
open val pathsToIgnore = listOf("/test/", "/testData/")
abstract val commitMessageRegex: Regex
open val ignorePattern: Regex = Regex("(?!.*)")
abstract fun isAvailable(): Boolean
internal fun containSources(files: Collection<VirtualFile>) =
fun containSources(files: Collection<VirtualFile>) =
files.asSequence()
.map { file -> Path.of(file.path) }
.any { path ->
@@ -35,31 +33,17 @@ internal abstract class IssueIDPrePushHandler : PrePushHandler {
&& pathsToIgnore.none { siPath.contains(it) }
}
fun commitMessageIsCorrect(message: String): Boolean = message.matches(commitMessageRegex) || message.matches(ignorePattern)
private fun handlerIsApplicable(project: Project): Boolean =
isAvailable() && IntelliJProjectUtil.isIntelliJPlatformProject(project)
companion object {
private val fileExtensionsNotToTrack = setOf("iml", "md")
private fun <T> invokeAndWait(modalityState: ModalityState, computable: () -> T): T {
val ref = AtomicReference<T>()
ApplicationManager.getApplication().invokeAndWait({ ref.set(computable.invoke()) }, modalityState)
return ref.get()
}
}
private fun handlerIsApplicable(project: Project): Boolean = isAvailable() && IntelliJProjectUtil.isIntelliJPlatformProject(project)
override fun handle(project: Project, pushDetails: MutableList<PushInfo>, indicator: ProgressIndicator): PrePushHandler.Result {
if (!handlerIsApplicable(project)) return PrePushHandler.Result.OK
return if (pushDetails.any { it.isTargetBranchProtected(project) && it.hasCommitsToEdit(indicator.modalityState) })
override fun handle(project: Project, pushDetails: MutableList<PushInfo>, indicator: ProgressIndicator): PrePushHandler.Result =
if (handlerIsApplicable(project) && pushDetails.any { isTargetBranchProtected(project, it) && it.complyTheRule(project, indicator.modalityState) }) {
PrePushHandler.Result.ABORT_AND_CLOSE
else PrePushHandler.Result.OK
}
} else {
PrePushHandler.Result.OK
}
private fun PushInfo.isTargetBranchProtected(project: Project) = GitSharedSettings.getInstance(project).isBranchProtected(pushSpec.target.presentation)
private fun PushInfo.hasCommitsToEdit(modalityState: ModalityState): Boolean {
protected fun PushInfo.complyTheRule(project: Project, modalityState: ModalityState): Boolean {
val commitsToWarnAbout = commits.asSequence()
.filter(::breaksMessageRules)
.map { it.id.toShortString() to it.subject }
@@ -67,6 +51,35 @@ internal abstract class IssueIDPrePushHandler : PrePushHandler {
if (commitsToWarnAbout.isEmpty()) return false
return doCommitsViolateRule(project, commitsToWarnAbout, modalityState)
}
abstract fun doCommitsViolateRule(project: Project, commitsToWarnAbout: List<Pair<String, String>>, modalityState: ModalityState): Boolean
protected open fun isTargetBranchProtected(project: Project, pushInfo: PushInfo): Boolean =
GitSharedSettings.getInstance(project).isBranchProtected(pushInfo.pushSpec.target.presentation)
protected open fun breaksMessageRules(commit: VcsFullCommitDetails): Boolean =
containSources(commit.changes.mapNotNull { it.virtualFile })
companion object {
internal val fileExtensionsNotToTrack = setOf("iml", "md")
internal fun <T> invokeAndWait(modalityState: ModalityState, computable: () -> T): T {
val ref = AtomicReference<T>()
ApplicationManager.getApplication().invokeAndWait({ ref.set(computable.invoke()) }, modalityState)
return ref.get()
}
}
}
internal abstract class IssueIDPrePushHandler : AbstractIntelliJProjectPrePushHandler() {
abstract val commitMessageRegex: Regex
open val ignorePattern: Regex = Regex("(?!.*)")
override val pathsToIgnore = listOf("/test/", "/testData/")
override fun doCommitsViolateRule(project: Project, commitsToWarnAbout: List<Pair<String, String>>, modalityState: ModalityState): Boolean {
val commitsInfo = commitsToWarnAbout.joinToString("<br/>") { hashAndSubject ->
"${hashAndSubject.first}: ${hashAndSubject.second}"
}
@@ -86,6 +99,10 @@ internal abstract class IssueIDPrePushHandler : PrePushHandler {
return !commitAsIs
}
private fun breaksMessageRules(commit: VcsFullCommitDetails) =
containSources(commit.changes.mapNotNull { it.virtualFile }) && !commitMessageIsCorrect(commit.fullMessage)
fun commitMessageIsCorrect(message: String): Boolean =
message.matches(commitMessageRegex) || message.matches(ignorePattern)
override fun breaksMessageRules(commit: VcsFullCommitDetails): Boolean {
return super.breaksMessageRules(commit) && !commitMessageIsCorrect(commit.fullMessage)
}
}

View File

@@ -1,6 +1,10 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.commit
import com.intellij.dvcs.push.PushInfo
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.MessageDialogBuilder
import com.intellij.openapi.util.registry.Registry
import org.jetbrains.annotations.Nls
@@ -23,4 +27,55 @@ internal class IntelliJPrePushHandler : IssueIDPrePushHandler() {
override fun isAvailable(): Boolean = Registry.`is`("intellij.commit.message.validation.enabled", true)
override fun getPresentableName(): @Nls String = DevKitGitBundle.message("push.commit.handler.idea.name")
}
internal class KotlinBuildToolsPrePushHandler: AbstractIntelliJProjectPrePushHandler() {
override val pathsToIgnore: List<String> =
listOf("community/plugins/kotlin/gradle/gradle-java/k1/test/org/jetbrains/kotlin/idea/scripting/")
// To be in sync with https://jetbrains.team/p/ij/repositories/ultimate-teamcity-config/files/master/.teamcity/src/idea/cherryPickRobot/branchReviewRules/BranchReviewRules.kt
// see approval(ReviewerGroups.kotlinBuildToolsTeam)
override val paths: List<String> =
listOf(
"community/plugins/kotlin/base/facet/",
"community/plugins/kotlin/base/jps/",
"community/plugins/kotlin/base/external-build-system/",
"community/plugins/kotlin/gradle/gradle/",
"community/plugins/kotlin/gradle/gradle-tooling/",
"community/plugins/kotlin/gradle/gradle-java/",
"community/plugins/kotlin/gradle/multiplatform-tests/",
"community/plugins/kotlin/gradle/multiplatform-tests-k2/",
"community/plugins/kotlin/jps/",
"community/plugins/kotlin/maven/"
)
override fun isAvailable(): Boolean =
Registry.`is`("kotlin.build.tools.code.ownership.commit.message.enabled", true)
override fun getPresentableName(): @Nls(capitalization = Nls.Capitalization.Title) String =
DevKitGitBundle.message("push.commit.kotlin.build.tools.handler.name")
override fun isTargetBranchProtected(project: Project, pushInfo: PushInfo): Boolean {
return super.isTargetBranchProtected(project, pushInfo) || pushInfo.pushSpec.target.presentation == "kt-master"
}
override fun doCommitsViolateRule(project: Project, commitsToWarnAbout: List<Pair<String, String>>, modalityState: ModalityState): Boolean {
val commitsInfo =
commitsToWarnAbout
.toList().joinToString("<br/>") { hashAndSubject ->
"${hashAndSubject.first}: ${hashAndSubject.second}"
}
invokeAndWait(modalityState) {
MessageDialogBuilder.okCancel(
DevKitGitBundle.message("push.commit.kotlin.build.tools.review.title"),
DevKitGitBundle.message("push.commit.kotlin.build.tools.message.lacks.issue.reference.body", commitsInfo)
)
.asWarning()
.ask(project = null)
}
return true
}
}