IJI-3207 DevKit: remove intellij.devkit.git, which contained only pre-push handlers logic

GitOrigin-RevId: a9d06e72ef255c3eaef4e6f7d3c4c2f0680eb67f
This commit is contained in:
Piotr Tomiak
2025-11-07 14:21:45 +00:00
committed by intellij-monorepo-bot
parent 703cb1fbc7
commit 6defd97d29
18 changed files with 0 additions and 1090 deletions
-1
View File
@@ -389,7 +389,6 @@
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.compose/intellij.devkit.compose.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.compose/intellij.devkit.compose.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/devkit-core/intellij.devkit.core.iml" filepath="$PROJECT_DIR$/plugins/devkit/devkit-core/intellij.devkit.core.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.debugger/intellij.devkit.debugger.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.debugger/intellij.devkit.debugger.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.git/intellij.devkit.git.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.git/intellij.devkit.git.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.gradle/intellij.devkit.gradle.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.gradle/intellij.devkit.gradle.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.i18n/intellij.devkit.i18n.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.i18n/intellij.devkit.i18n.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.intelliLang/intellij.devkit.intelliLang.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.intelliLang/intellij.devkit.intelliLang.iml" />
-1
View File
@@ -884,7 +884,6 @@ plugins/devkit/devkit-kotlin-tests
plugins/devkit/devkit-tests
plugins/devkit/intellij.devkit.compose
plugins/devkit/intellij.devkit.debugger
plugins/devkit/intellij.devkit.git
plugins/devkit/intellij.devkit.gradle
plugins/devkit/intellij.devkit.i18n
plugins/devkit/intellij.devkit.intelliLang
-2
View File
@@ -57,7 +57,6 @@ jvm_library(
"//plugins/devkit",
"//plugins/devkit/devkit-core:core",
"//plugins/devkit/intellij.devkit.debugger:debugger",
"//plugins/devkit/intellij.devkit.git:git",
"//plugins/devkit/intellij.devkit.gradle:gradle",
"//plugins/devkit/intellij.devkit.i18n:i18n",
"//plugins/devkit/intellij.devkit.intelliLang:intelliLang",
@@ -81,7 +80,6 @@ jvm_library(
"//plugins/devkit",
"//plugins/devkit/devkit-core:core",
"//plugins/devkit/intellij.devkit.debugger:debugger",
"//plugins/devkit/intellij.devkit.git:git",
"//plugins/devkit/intellij.devkit.gradle:gradle",
"//plugins/devkit/intellij.devkit.i18n:i18n",
"//plugins/devkit/intellij.devkit.intelliLang:intelliLang",
@@ -708,10 +708,6 @@
<registryKey key="tmh.generate.line.numbers" defaultValue="true"
description="Generate line numbers when instrumenting @RequiresEdt and similar annotations.
This results in better stack traces when generated assertions throw exceptions, but might slow down the compilation."/>
<registryKey key="kotlin.notebook.commit.message.validation.enabled" defaultValue="true"
description="Enables commit messages validation for Kotlin Notebook plugin"/>
<registryKey key="kotlin.build.tools.code.ownership.commit.message.enabled" defaultValue="true"
description="Enables commit messages validation for Kotlin Build Tools code ownership"/>
<codeInsight.declarativeInlayProvider
providerId="devkit.threading"
bundle="messages.DevKitBundle"
@@ -724,13 +720,6 @@
<registryKey key="devkit.inlay.threading" defaultValue="false"
description="Experimental inlays for Threading Annotations"/>
<registryKey key="intellij.monorepo.commit.message.validation.enabled" defaultValue="true"
description="Enables commit messages validation for IntelliJ Monorepo"/>
<registryKey key="aia.commit.message.validation.enabled" defaultValue="true"
description="Enables commit messages validation for AI Assistant project"/>
<registryKey key="toolbox.commit.message.validation.enabled" defaultValue="true"
description="Enables commit messages validation for Toolbox project"/>
<generatedSourcesFilter implementation="org.jetbrains.idea.devkit.contentReport.ContentReportGeneratedSourcesFilter"/>
<!-- INTERNAL -->
@@ -1,107 +0,0 @@
### auto-generated section `build intellij.devkit.git` start
load("@rules_jvm//:jvm.bzl", "jvm_library", "jvm_provided_library", "resourcegroup")
resourcegroup(
name = "git_resources",
srcs = glob(["resources/**/*"]),
strip_prefix = "resources"
)
jvm_provided_library(
name = "platform_vcs-api_vcs-core_provided",
lib = "//platform/vcs-api/vcs-api-core:vcs-core"
)
jvm_provided_library(
name = "platform_vcs-log_vcs-log_provided",
lib = "//platform/vcs-log/api:vcs-log"
)
jvm_provided_library(
name = "platform_vcs-dvcs-impl_provided",
lib = "//platform/dvcs-impl:vcs-dvcs-impl"
)
jvm_provided_library(
name = "plugins_vcs-git_provided",
lib = "//plugins/git4idea:vcs-git"
)
jvm_library(
name = "git",
module_name = "intellij.devkit.git",
visibility = ["//visibility:public"],
srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True),
resources = [":git_resources"],
deps = [
"@lib//:kotlin-stdlib",
"//platform/util-ex",
"//platform/core-api:core",
"//platform/projectModel-api:projectModel",
"//platform/util",
"//platform/ide-core",
"//plugins/devkit/devkit-core:core",
"//platform/vcs-api/shared",
"//platform/vcs-log/impl",
"//platform/platform-api:ide",
":platform_vcs-api_vcs-core_provided",
":platform_vcs-log_vcs-log_provided",
":platform_vcs-dvcs-impl_provided",
":plugins_vcs-git_provided",
]
)
jvm_provided_library(
name = "platform_vcs-api_vcs-core_test_lib_provided",
lib = "//platform/vcs-api/vcs-api-core:vcs-core_test_lib"
)
jvm_provided_library(
name = "platform_vcs-dvcs-impl_test_lib_provided",
lib = "//platform/dvcs-impl:vcs-dvcs-impl_test_lib"
)
jvm_provided_library(
name = "plugins_vcs-git_test_lib_provided",
lib = "//plugins/git4idea:vcs-git_test_lib"
)
jvm_library(
name = "git_test_lib",
visibility = ["//visibility:public"],
srcs = glob(["test/**/*.kt", "test/**/*.java", "test/**/*.form"], allow_empty = True),
associates = [":git"],
deps = [
"@lib//:kotlin-stdlib",
"//platform/util-ex",
"//platform/core-api:core",
"//platform/projectModel-api:projectModel",
"//platform/util",
"//platform/ide-core",
"//plugins/devkit/devkit-core:core",
"@lib//:junit4",
"//platform/testFramework",
"//platform/testFramework:testFramework_test_lib",
"//platform/vcs-api/shared",
"//platform/vcs-log/impl",
"//platform/vcs-log/impl:impl_test_lib",
"//platform/platform-api:ide",
":platform_vcs-api_vcs-core_provided",
":platform_vcs-log_vcs-log_provided",
":platform_vcs-dvcs-impl_provided",
":plugins_vcs-git_provided",
":platform_vcs-api_vcs-core_test_lib_provided",
":platform_vcs-dvcs-impl_test_lib_provided",
":plugins_vcs-git_test_lib_provided",
]
)
### auto-generated section `build intellij.devkit.git` end
### auto-generated section `test intellij.devkit.git` start
load("@community//build:tests-options.bzl", "jps_test")
jps_test(
name = "git_test",
runtime_deps = [":git_test_lib"]
)
### auto-generated section `test intellij.devkit.git` end
@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="org.jetbrains.idea.devkit.commit" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" packagePrefix="org.jetbrains.idea.devkit.commit" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.platform.util.ex" />
<orderEntry type="module" module-name="intellij.platform.core" />
<orderEntry type="module" module-name="intellij.platform.projectModel" />
<orderEntry type="module" module-name="intellij.platform.util" />
<orderEntry type="module" module-name="intellij.platform.ide.core" />
<orderEntry type="module" module-name="intellij.platform.vcs.core" scope="PROVIDED" />
<orderEntry type="module" module-name="intellij.platform.vcs.log" scope="PROVIDED" />
<orderEntry type="module" module-name="intellij.platform.vcs.dvcs.impl" scope="PROVIDED" />
<orderEntry type="module" module-name="intellij.vcs.git" scope="PROVIDED" />
<orderEntry type="module" module-name="intellij.devkit.core" />
<orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.vcs.shared" />
<orderEntry type="module" module-name="intellij.platform.vcs.log.impl" />
<orderEntry type="module" module-name="intellij.platform.ide" />
</component>
</module>
@@ -1,13 +0,0 @@
<idea-plugin package="org.jetbrains.idea.devkit.commit">
<dependencies>
<module name="intellij.devkit.core"/>
<plugin id="Git4Idea"/>
<module name="intellij.platform.vcs.log.impl"/>
</dependencies>
<extensions defaultExtensionNs="com.intellij">
<prePushHandler implementation="org.jetbrains.idea.devkit.commit.KotlinBuildToolsPrePushHandler"/>
<prePushHandler implementation="org.jetbrains.idea.devkit.commit.KotlinNotebookPluginPrePushHandler"/>
<prePushHandler implementation="org.jetbrains.idea.devkit.commit.IntelliJMonorepoPrePushHandler"/>
<prePushHandler implementation="org.jetbrains.idea.devkit.commit.ToolboxPrePushHandler"/>
</extensions>
</idea-plugin>
@@ -1,38 +0,0 @@
push.commit.message.lacks.issue.reference.body=\
<html>Commits listed below change the Kotlin IDE plugin sources. According to the rules of the plugin team, \
<a href=https://github.com/JetBrains/intellij-community/blob/master/plugins/kotlin/README.md#2-commits-requirements>all meaningful commits</a> \
should reference a {0} issue in their message. Do you still want to push as is? \
<pre>{1}</pre> \
</html>
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.kotlin.notebook.handler.name=Kotlin Notebook Plugin Push Handler
push.commit.intellij.platform.handler.name=IntelliJ Monorepo Push Handler
push.commit.intellij.platform.handler.title=Please Mention a YouTrack Issue in Commit Messages
push.commit.intellij.platform.message.lacks.issue.reference.body=\
<html>Commits listed below change the JetBrains IDEs product's code. \
Please follow a commit message format (see README.md in mono repository root) and reference a YouTrack issue in commit messages which change modifies product's distribution.<br/><br/> \
<a href="https://youtrack.jetbrains.com/newIssue">Create</a> an issue or use any IDE-related YT ticket ID.<br/><br/> \
</html>
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>
toolbox.commit.handler.name=Toolbox Push Handler
toolbox.push.commit.message.lacks.issue.reference.title=Please Mention a YouTrack Issue in Commit Messages
toolbox.push.commit.message.lacks.issue.reference.body=\
<html>Commits listed below change the Toolbox or Station code. <br/> \
Please reference a <a href="https://youtrack.jetbrains.com/newIssue?project=TBX">TBX</a> issue in the commit messages. <br/> \
Issues from other projects, like <a href="https://youtrack.jetbrains.com/newIssue?project=IJPL">IJPL</a> or <a href="https://youtrack.jetbrains.com/newIssue?project=IDES">IDES</a> could be used as well. <br/> \
Do you still want to push as is? <br/>\
<br><code>{0}</code> \
</html>
@@ -1,78 +0,0 @@
// Copyright 2000-2024 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.PrePushHandler
import com.intellij.dvcs.push.PushInfo
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.project.IntelliJProjectUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.vcs.changes.ContentRevision
import com.intellij.vcs.log.VcsFullCommitDetails
import git4idea.config.GitSharedSettings
import java.nio.file.Path
import java.util.concurrent.atomic.AtomicReference
import kotlin.io.path.extension
import kotlin.io.path.invariantSeparatorsPathString
internal abstract class AbstractIntelliJProjectPrePushHandler : PrePushHandler {
protected abstract fun isAvailable(): Boolean
private fun handlerIsApplicable(project: Project): Boolean =
isAvailable() && IntelliJProjectUtil.isIntelliJPlatformProject(project)
final override fun handle(project: Project, pushDetails: List<PushInfo>, indicator: ProgressIndicator): PrePushHandler.Result {
if (!handlerIsApplicable(project)) {
return PrePushHandler.Result.OK
}
if (pushDetails.any {
isTargetBranchProtected(project, it)
&& validate(project, it, indicator) == PushInfoValidationResult.INVALID
}) {
return PrePushHandler.Result.ABORT_AND_CLOSE
}
return PrePushHandler.Result.OK
}
protected open fun isTargetBranchProtected(project: Project, pushInfo: PushInfo): Boolean =
GitSharedSettings.getInstance(project).isBranchProtected(pushInfo.pushSpec.target.presentation)
protected abstract fun validate(project: Project, info: PushInfo, indicator: ProgressIndicator): PushInfoValidationResult
protected enum class PushInfoValidationResult {
VALID, INVALID, SKIP
}
companion object {
@JvmStatic
protected val ContentRevision.path: Path?
get() = file.takeIf { !it.isNonLocal }?.ioFile?.toPath() // TODO: handle non-local?
private val fileExtensionsNotToTrack = setOf("iml", "md")
@JvmStatic
protected fun Sequence<Path>.anyIn(paths: List<String>, pathsToIgnore: List<String>): Boolean =
any { path ->
val siPath = path.invariantSeparatorsPathString
path.extension !in fileExtensionsNotToTrack
&& paths.any { siPath.contains(it) }
&& pathsToIgnore.none { siPath.contains(it) }
}
@JvmStatic
protected fun List<VcsFullCommitDetails>.toHtml(): @NlsSafe String =
joinToString("<br/>") { commit ->
"${commit.id.toShortString()}: ${commit.subject}"
}
internal fun <T> invokeAndWait(modalityState: ModalityState, computable: () -> T): T {
val ref = AtomicReference<T>()
ApplicationManager.getApplication().invokeAndWait({ ref.set(computable.invoke()) }, modalityState)
return ref.get()
}
}
}
@@ -1,30 +0,0 @@
// Copyright 2000-2022 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.DynamicBundle;
import org.jetbrains.annotations.*;
import java.util.function.Supplier;
final class DevKitGitBundle {
private static final @NonNls String BUNDLE_FQN = "messages.DevKitGitBundle";
private static final DynamicBundle BUNDLE = new DynamicBundle(DevKitGitBundle.class, BUNDLE_FQN);
private DevKitGitBundle() {
}
public static @Nls @NotNull String message(
@PropertyKey(resourceBundle = BUNDLE_FQN) @NotNull String key,
@Nullable Object @NotNull ... params
) {
return BUNDLE.getMessage(key, params);
}
public static @NotNull Supplier<@Nls @NotNull String> messagePointer(
@PropertyKey(resourceBundle = BUNDLE_FQN) @NotNull String key,
@Nullable Object @NotNull ... params
) {
return BUNDLE.getLazyMessage(key, params);
}
}
@@ -1,122 +0,0 @@
// Copyright 2000-2025 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.progress.ProgressIndicator
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.MessageDialogBuilder
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import com.intellij.vcs.log.VcsFullCommitDetails
import com.intellij.vcs.log.impl.VcsProjectLog
import org.jetbrains.annotations.VisibleForTesting
import java.nio.file.Path
internal abstract class IssueIDPrePushHandler : AbstractIntelliJProjectPrePushHandler() {
protected open val acceptableProjects = listOf(
"KTIJ", "KTNB", "KT", "IDEA", "IJPL"
)
protected abstract val commitMessageRegex: Regex
protected open val ignorePattern: Regex = Regex("(?!.*)")
protected open val paths: List<String> = listOf()
protected open val pathsToIgnore: List<String> = listOf("/test/", "/testData/")
protected open val validateCommitsOnlyFromCurrentUser: Boolean = false
final override fun validate(project: Project, info: PushInfo, indicator: ProgressIndicator): PushInfoValidationResult {
val commitsToWarnAbout = info.commits.filterNot { isCommitValid(project, it) }
if (commitsToWarnAbout.isEmpty()) {
return PushInfoValidationResult.VALID
}
val skip = handleCommitsValidationFailure(project, info, commitsToWarnAbout, indicator.modalityState)
if (skip) return PushInfoValidationResult.SKIP
return PushInfoValidationResult.INVALID
}
private fun isCommitValid(project: Project, commit: VcsFullCommitDetails): Boolean {
if (validateCommitsOnlyFromCurrentUser) {
val currentUsers = VcsProjectLog.getInstance(project).dataManager?.userNameResolver?.resolveCurrentUser(commit.root)
val committer = commit.committer
// allow commits from other people:
if (currentUsers != null && committer.email !in currentUsers.map { it.email }) {
return true
}
}
val commitPaths = commit.changes.asSequence().flatMap { change ->
sequenceOf(change.beforeRevision?.path, change.afterRevision?.path).filterNotNull()
}
if (!containSources(commitPaths)) {
return true
}
return isCommitMessageCorrect(commit.fullMessage)
}
@VisibleForTesting
fun containSources(sourcePaths: Sequence<Path>): Boolean = sourcePaths.anyIn(paths, pathsToIgnore)
/**
* Notify the user about commits that don't pass validation.
* Commits in [commitsToWarnAbout] are ordered from oldest to newest.
*
* @return `true` if user decided to skip validation, `false` otherwise.
*/
@RequiresBackgroundThread
protected open fun handleCommitsValidationFailure(
project: Project,
info: PushInfo,
commitsToWarnAbout: List<VcsFullCommitDetails>,
modalityState: ModalityState,
): Boolean {
val lastAcceptableProjectIndex = acceptableProjects.lastIndex
val acceptableProjectIssueLinks = acceptableProjects.mapIndexed { index, projectId ->
val link = getProjectIssueLink(projectId)
val suffix = when(index) {
lastAcceptableProjectIndex -> ""
lastAcceptableProjectIndex - 1 -> " or "
else -> ", "
}
link + suffix
}.joinToString("")
val commitsInfo = commitsToWarnAbout.toHtml()
val commitAsIs = invokeAndWait(modalityState) {
@Suppress("DialogTitleCapitalization")
MessageDialogBuilder.yesNo(
DevKitGitBundle.message("push.commit.message.lacks.issue.reference.title"),
DevKitGitBundle.message(
"push.commit.message.lacks.issue.reference.body",
acceptableProjectIssueLinks,
commitsInfo
)
)
.yesText(DevKitGitBundle.message("push.commit.message.lacks.issue.reference.commit"))
.noText(DevKitGitBundle.message("push.commit.message.lacks.issue.reference.edit"))
.asWarning()
.ask(project = null)
}
return commitAsIs
}
fun isCommitMessageCorrect(message: String): Boolean {
if (message == "Rename .java to .kt") {
return true
}
return message.matches(commitMessageRegex) || message.matches(ignorePattern)
}
protected fun buildRegexFromAcceptableProjects(): Regex {
@Suppress("RegExpUnnecessaryNonCapturingGroup")
return Regex(
".*(?:${acceptableProjects.joinToString("|")})-\\d+.*",
RegexOption.DOT_MATCHES_ALL /* line breaks matter */
)
}
private fun getProjectIssueLink(projectId: String) =
"<a href=\"https://youtrack.jetbrains.com/newIssue?project=$projectId\">$projectId</a>"
}
@@ -1,140 +0,0 @@
// Copyright 2000-2025 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.components.service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.util.registry.Registry
import com.intellij.ui.components.JBScrollPane
import com.intellij.vcs.log.VcsFullCommitDetails
import git4idea.rebase.GitInteractiveRebaseService
import git4idea.repo.GitRepository
import org.jetbrains.annotations.Nls
import java.awt.BorderLayout
import java.awt.Dimension
import javax.swing.Action
import javax.swing.JComponent
import javax.swing.JEditorPane
import javax.swing.JPanel
internal class IntelliJMonorepoPrePushHandler : IssueIDPrePushHandler() {
private val protectedBranches = listOf("ij-ai/", "ij-aia/", "ide-next/")
override val paths: List<String> = listOf(
"/community/platform/",
"remote-dev", // ij platform
"plugins/kotlin/", // kotlin plugin
"plugins/llm/", "plugins/llm-installer/", "plugins/full-line/", // aia plugin
)
override val pathsToIgnore: List<String> = super.pathsToIgnore.toMutableList()
.apply { add("/fleet/plugins/kotlin/") }
.apply { add("/plugins/kotlin/jupyter/") }
override val commitMessageRegex = Regex("""(?:^|.*[^-A-Z0-9])[A-Z]+-\d+.*""", RegexOption.DOT_MATCHES_ALL)
override val ignorePattern = Regex(
pattern = """^(?:\[.+] ?)?\[?(?:tests?|clean ?up|docs?|typo|refactor(?:ing)?|format|style|testFramework|test framework)]?.*\s.*[A-Z0-9](?:.|\n)*|(?:.*[^a-z])?WIP(?:[^a-z](?:.|\n)*)?""",
option = RegexOption.IGNORE_CASE
)
override val validateCommitsOnlyFromCurrentUser: Boolean = true
override fun isTargetBranchProtected(project: Project, pushInfo: PushInfo): Boolean {
return super.isTargetBranchProtected(project, pushInfo) || protectedBranches.any { pushInfo.pushSpec.target.presentation.startsWith(it) }
}
override fun isAvailable(): Boolean = Registry.`is`("intellij.monorepo.commit.message.validation.enabled", true)
override fun getPresentableName(): @Nls String = DevKitGitBundle.message("push.commit.intellij.platform.handler.name")
override fun handleCommitsValidationFailure(
project: Project,
info: PushInfo,
commitsToWarnAbout: List<VcsFullCommitDetails>,
modalityState: ModalityState,
): Boolean {
val commitsInfo = commitsToWarnAbout.toHtml()
val result = invokeAndWait(modalityState) {
val dialog = CommitValidationDialog(project, commitsInfo)
dialog.show()
dialog.exitCode
}
if (result == CommitValidationDialog.EDIT_EXIT_CODE) {
val repository = info.repository as? GitRepository ?: run {
thisLogger().error("Unexpected repository type: ${info.repository}")
return false
}
project.service<GitInteractiveRebaseService>().launchRebase(repository, commitsToWarnAbout.first())
}
return result == DialogWrapper.OK_EXIT_CODE
}
}
private class CommitValidationDialog(
project: Project,
private val commitsInfo: String
) : DialogWrapper(project) {
companion object {
const val EDIT_EXIT_CODE = NEXT_USER_EXIT_CODE + 1
}
private val editAction = object : DialogWrapperAction(DevKitGitBundle.message("push.commit.message.lacks.issue.reference.edit")) {
override fun doAction(e: java.awt.event.ActionEvent) {
close(EDIT_EXIT_CODE)
}
}
private val commitAction = object : DialogWrapperAction(DevKitGitBundle.message("push.commit.message.lacks.issue.reference.commit")) {
override fun doAction(e: java.awt.event.ActionEvent) {
close(OK_EXIT_CODE)
}
}
init {
title = DevKitGitBundle.message("push.commit.intellij.platform.handler.title")
isResizable = true
init()
rootPane.defaultButton = getButton(editAction)
}
override fun createCenterPanel(): JComponent {
val panel = JPanel(BorderLayout())
val explanationPane = JEditorPane("text/html", DevKitGitBundle.message("push.commit.intellij.platform.message.lacks.issue.reference.body", commitsInfo)).apply {
isEditable = false
background = panel.background
putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, true)
border = null
}
@Suppress("HardCodedStringLiteral")
val commitInfoPane = JEditorPane("text/html", "<pre>${commitsInfo.replace("\n", "<br/>")}</pre>").apply {
isEditable = false
background = panel.background
}
val scrollPane = JBScrollPane(commitInfoPane).apply {
minimumSize = Dimension(400, 150)
preferredSize = Dimension(600, 300)
verticalScrollBarPolicy = JBScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED
horizontalScrollBarPolicy = JBScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED
border = null
}
panel.add(explanationPane, BorderLayout.NORTH)
panel.add(scrollPane, BorderLayout.CENTER)
return panel
}
override fun createActions(): Array<Action> {
return arrayOf(
commitAction,
editAction,
cancelAction
)
}
override fun getDimensionServiceKey(): String {
return "IntelliJPlatformCommitValidationDialog"
}
}
@@ -1,76 +0,0 @@
// Copyright 2000-2025 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.progress.ProgressIndicator
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.MessageDialogBuilder
import com.intellij.openapi.util.registry.Registry
import org.jetbrains.annotations.Nls
import java.nio.file.Path
internal class KotlinNotebookPluginPrePushHandler : IssueIDPrePushHandler() {
override val paths: List<String> = listOf("plugins/kotlin/jupyter/")
override val acceptableProjects: List<String> = super.acceptableProjects + listOf(
"PY"
)
override val commitMessageRegex = buildRegexFromAcceptableProjects()
override fun isAvailable(): Boolean = Registry.`is`("kotlin.notebook.commit.message.validation.enabled", true)
override fun getPresentableName(): String = DevKitGitBundle.message("push.commit.kotlin.notebook.handler.name")
}
private val KOTLIN_BUILD_TOOLS_PATHS_TO_IGNORE: 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)
private val KOTLIN_BUILD_TOOLS_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/"
)
internal class KotlinBuildToolsPrePushHandler : AbstractIntelliJProjectPrePushHandler() {
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 validate(project: Project, info: PushInfo, indicator: ProgressIndicator): PushInfoValidationResult {
val matchingCommits = info.commits.filter { commit ->
commit.changes.asSequence()
.mapNotNull { it.virtualFile }
.map { Path.of(it.path) }
.anyIn(KOTLIN_BUILD_TOOLS_PATHS, KOTLIN_BUILD_TOOLS_PATHS_TO_IGNORE)
}
if (matchingCommits.isNotEmpty()) {
val commitsInfo = matchingCommits.toHtml()
invokeAndWait(indicator.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 PushInfoValidationResult.VALID
}
}
@@ -1,52 +0,0 @@
// Copyright 2000-2025 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 com.intellij.vcs.log.VcsFullCommitDetails
internal class ToolboxPrePushHandler : IssueIDPrePushHandler() {
override val paths = listOf("toolbox/", "station/")
override val acceptableProjects: List<String> = super.acceptableProjects + listOf(
"TBX", "IDES"
)
private val acceptableGroup = listOf(
"docs", "doc",
"cleanup", "typo",
"refactoring", "refactor", "format",
"tests", "test"
)
private val groupPattern = Regex("""\b(${acceptableGroup.joinToString("|")})\b.*""", RegexOption.IGNORE_CASE)
override val commitMessageRegex: Regex = Regex(
"(${buildRegexFromAcceptableProjects().pattern})|(${groupPattern.pattern})",
setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE)
)
override fun isAvailable(): Boolean = Registry.`is`("toolbox.commit.message.validation.enabled", true)
override fun getPresentableName(): String = DevKitGitBundle.message("toolbox.commit.handler.name")
override fun handleCommitsValidationFailure(project: Project, info: PushInfo, commitsToWarnAbout: List<VcsFullCommitDetails>, modalityState: ModalityState): Boolean {
val commitsInfo = commitsToWarnAbout.toHtml()
val commitAsIs = invokeAndWait(modalityState) {
@Suppress("DialogTitleCapitalization")
MessageDialogBuilder.yesNo(
DevKitGitBundle.message("toolbox.push.commit.message.lacks.issue.reference.title"),
DevKitGitBundle.message("toolbox.push.commit.message.lacks.issue.reference.body", commitsInfo)
)
.yesText(DevKitGitBundle.message("push.commit.message.lacks.issue.reference.commit"))
.noText(DevKitGitBundle.message("push.commit.message.lacks.issue.reference.edit"))
.asWarning()
.ask(project)
}
return commitAsIs
}
}
@@ -1,387 +0,0 @@
// 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.openapi.application.PathManager
import org.junit.Test
import org.junit.experimental.runners.Enclosed
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameter
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.div
import kotlin.io.path.exists
import kotlin.io.path.readText
@RunWith(Enclosed::class)
class IntelliJMonorepoPrePushHandlerTest {
@RunWith(Parameterized::class)
class FilesBelongToPlatform {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun provideTestParameters(): Iterable<List<String>> {
return listOf(
listOf("${COMMUNITY_PLATFORM}/File.kt"),
listOf(
"${COMMUNITY_PLATFORM}/File.kt",
"root.txt",
),
listOf(
"${COMMUNITY_PLATFORM}/File.kt",
"${COMMUNITY}/plugins/stuff.xml",
"not-important-dir-A/whatever",
"not-important-dir-B/whatever"
),
listOf("unrelated/$COMMUNITY_PLATFORM/File.kt"),
)
}
}
@Parameter
lateinit var files: List<String>
@Test
fun testThatChecksForFileSet() {
val filesSet = files.asSequence().map { pathAt(it) }
assert(prePushHandler.containSources(filesSet)) {
"The following set of files doesn't trigger the check: $filesSet"
}
}
}
@RunWith(Parameterized::class)
class FilesDoNotBelongToPlatform {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun provideTestParameters(): Iterable<List<String>> {
return listOf(
listOf("${COMMUNITY_PLATFORM}/README.md"),
listOf("${COMMUNITY_PLATFORM}/sub/sub.iml"),
listOf("${COMMUNITY}/File.kt"),
listOf("${COMMUNITY}/README.md"),
listOf("${COMMUNITY}/sub/sub.iml"),
listOf(
"${COMMUNITY}/plugins/stuff.xml",
"not-important-dir-A/whatever",
"not-important-dir-B/whatever",
),
)
}
}
@Parameter
lateinit var files: List<String>
@Test
fun testThatIgnoresFileSet() {
val filesSet = files.asSequence().map { pathAt(it) }
assert(!prePushHandler.containSources(filesSet)) {
"The following set of files triggered the check: $filesSet"
}
}
}
@RunWith(Parameterized::class)
class ValidCommitMessages {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun provideTestParameters(): Iterable<String> {
return listOf(
"IJPL-1", "A-1",
"IJPL-123", "(IJPL-123)", "[IJPL-123]", "{IJPL-123}", "'IJPL-123'", "`IJPL-123`", "\"IJPL-123\"",
"IDEA-123", "(IDEA-123)", "[IDEA-123]", "{IDEA-123}", "'IDEA-123'", "`IDEA-123`", "\"IDEA-123\"",
"IJPL-42",
"KTIJ-123 ", " KTIJ-123", " KTIJ-123 ", "IDEA-123 ",
" IDEA-123", " IDEA-123 ",
"IJPL-123 header",
"Header IJPL-123",
"[sub system 123] docs: fix typo",
"Header IJPL-123 header",
"IJPL-123 header",
"Header IJPL-123",
"Header IJPL-123 header",
"""
Header IJPL-123 header
Body-line-1
Body-line-N
""".trimIndent(),
"""
Header IJPL-123 header
Body-line-1
Body-line-N
""".trimIndent(),
"""
Header IJPL-123 header
Body-line-1
Body-line-N
^IJPL-123 fixed
""".trimIndent(),
"""
Header IJPL-123 header
Body-line-1
Body-line-N
^IDEA-123 fixed
""".trimIndent(),
"""
Header IJPL-42 header
Body-line-1
Body-line-N
^IDEA-123 fixed
""".trimIndent(),
"""
Header
Body-line-1
Body-line-N
^IJPL-123 fixed
""".trimIndent(),
"""
Header
Body-line-1
Body-line-N
^IDEA-123 fixed
""".trimIndent(),
"""
Header
Body-line-1
Body-line-N
#KTIJ-123 fixed
""".trimIndent(),
"""
Header
Body-line-1
Body-line-N
#IDEA-123 fixed
""".trimIndent(),
"""
Header
Body-line-1
Body-line-N
Relates to #KTIJ-123
""".trimIndent(),
"""
Header
Body-line-1
Body-line-N
Relates to #IDEA-123
""".trimIndent(),
"""
Header
Body-line-1
Body-line-N
^WHATEVER-123 fixed
""".trimIndent(),
"""
Header
Body-line-1
Body-line-N
#WHATEVER-123 fixed
""".trimIndent(),
"""
Header
Body-line-1
Body-line-N
Relates to #WHATEVER-123
""".trimIndent(),
"""
[subsystem] refactoring: rename a to b
Explains why it is needed.
""".trimIndent(),
"test thing", "test: thing", "[test] thing", "Test thing",
"tests thing", "tests: thing", "[tests] thing",
"cleanup thing", "cleanup: thing", "[cleanup] thing", "Cleanup stuff",
"clean up thing", "clean up: thing", "[clean up] thing", "Clean Up stuff",
"docs thing", "docs: thing", "[docs] thing", "Docs thing",
"doc thing", "doc: thing", "[doc] thing", "Doc very much",
"typo thing", "typo: thing", "[typo] thing",
"format thing", "format: thing", "[format] thing",
"style thing", "style: thing", "[style] thing",
"refactor this thing", "refactor: this thing",
"WIP", "[WIP] do stuff", "Add thingies WIP", "wip", "(wip) hoho", "wip: haha",
"""
WIP
Some additional remarks.
""".trimIndent(),
"Cleanup (reason)",
"[subsystem][tests] new tests",
"[subsystem] docs: fix typo",
"[smth][refactoring] do stuff",
"[testFramework] whatever",
"[test framework] whatever",
"test framework",
"Rename .java to .kt"
)
}
}
@Parameter
lateinit var commitMessage: String
@Test
fun testThatCommitMessageIsValid() {
assert(prePushHandler.isCommitMessageCorrect(commitMessage)) {
"The following commit message was considered invalid: $commitMessage"
}
}
}
@RunWith(Parameterized::class)
class InvalidCommitMessages {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun provideTestParameters(): Iterable<String> {
return listOf(
"",
"IJPL",
"IJPL-",
"IJPL-one",
"IJPL 1",
"IJPL111",
"Text IJPL Text",
"something-something",
"2-fold",
"AB-RA-CA-DA-BRA-1",
""""
Header
Body-line-1
Body-line-N
""".trimIndent(),
"""
No explanation
Test added
""".trimIndent(),
"""
No explanation
WIP
""".trimIndent(),
"test", "test:", "[test]", "test ", "add test", "drop test",
"tests", "tests:", "[tests]", "tests ", "add tests", "drop tests",
"cleanup", "cleanup:", "[cleanup]", "cleanup ", "do cleanup",
"clean up", "clean up:", "[clean up]", "clean up ", "do clean up",
"docs", "docs:", "[docs]", "docs ", "add docs",
"doc", "doc:", "[doc]", "doc ", "add doc",
"typo", "typo:", "[typo]", "typo ", "fix typo",
"format", "format:", "[format]", "fix format", "fix formatting",
"style", "style:", "[style]", "fix style",
"stuff", "stuff:", "[stuff]", "very important stuff",
"refactor", "refactor:", "[refactor]",
"refactoring", "refactoring:", "[refactoring]",
"wipe", "swipe", "wipe the db",
"[subsystem] Do very important stuff very very important",
"platform: add 'thing' here and there",
"subsystem tests: use junit5 instead of junit4",
"subsystem: cleanup",
"cool: 5 new tests",
"Fix test",
"[subsystem] docs",
"null",
"IJ-CR-160532 do not highlight parser errors twice for java",
"IJ-MR-123", "IJ-CR-0",
)
}
}
@Parameter
lateinit var commitMessage: String
@Test
fun testThatCommitMessageIsValid() {
assert(!prePushHandler.isCommitMessageCorrect(commitMessage)) {
"The following commit message was considered as valid: $commitMessage"
}
}
}
class ContributingMdCommitMessagesAreValid {
@Test
fun testCommitMessagesFromContributingMd() {
val contributingFile = Path.of(PathManager.getCommunityHomePath()) / "CONTRIBUTING.md"
assert(contributingFile.exists()) { "CONTRIBUTING.md file not found at expected location" }
val content = contributingFile.readText()
val commitMessages = extractCommitMessagesFromMarkdown(content)
assert(commitMessages.isNotEmpty()) { "No commit messages found in CONTRIBUTING.md" }
commitMessages.forEach { commitMessage ->
assert(prePushHandler.isCommitMessageCorrect(commitMessage)) {
"Commit message from CONTRIBUTING.md is invalid: '$commitMessage'"
}
}
}
private fun extractCommitMessagesFromMarkdown(content: String): List<String> {
val codeBlockRegex = Regex("```(.*?)```", RegexOption.DOT_MATCHES_ALL)
val commitMessages = mutableListOf<String>()
codeBlockRegex.findAll(content).forEach { match ->
val codeBlock = match.groupValues[1].trim()
if (codeBlock.contains("[<") && codeBlock.contains(">]")) {
return@forEach
}
if (codeBlock.isNotEmpty()) {
commitMessages.add(codeBlock)
}
}
return commitMessages
}
}
}
private val prePushHandler = IntelliJMonorepoPrePushHandler()
private val tempDir: String = System.getProperty("java.io.tmpdir")
private const val COMMUNITY_PLATFORM = "community/platform/"
private const val COMMUNITY = "community/"
private fun pathAt(path: String): Path {
return Paths.get(tempDir, *path.split("/").filterNot { it.isEmpty() }.toTypedArray())
}
@@ -7,7 +7,6 @@
<orderEntry type="module" module-name="intellij.devkit" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.devkit.core" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.devkit.debugger" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.devkit.git" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.devkit.gradle" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.devkit.i18n" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.devkit.intelliLang" scope="RUNTIME" />
-1
View File
@@ -9,7 +9,6 @@
- name: intellij.devkit
contentModules:
- name: intellij.devkit.core
- name: intellij.devkit.git
- name: intellij.devkit.gradle
- name: intellij.devkit.i18n
- name: intellij.devkit.intelliLang
@@ -29,7 +29,6 @@
<content namespace="jetbrains">
<module name="intellij.devkit.core"/>
<module name="intellij.devkit.compose"/>
<module name="intellij.devkit.git"/>
<module name="intellij.devkit.gradle"/>
<module name="intellij.devkit.i18n"/>
<module name="intellij.devkit.intelliLang"/>