diff --git a/plugins/changeReminder/src/com/jetbrains/changeReminder/predict/FileProbabilityRequest.kt b/plugins/changeReminder/src/com/jetbrains/changeReminder/predict/FileProbabilityRequest.kt new file mode 100644 index 000000000000..62e67daa23b7 --- /dev/null +++ b/plugins/changeReminder/src/com/jetbrains/changeReminder/predict/FileProbabilityRequest.kt @@ -0,0 +1,35 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.changeReminder.predict + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vcs.FilePath +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.vcs.log.data.VcsLogData +import com.jetbrains.changeReminder.getGitRootFiles +import com.jetbrains.changeReminder.repository.Commit +import com.jetbrains.changeReminder.repository.FilesHistoryProvider + +class FileProbabilityRequest(private val project: Project, + private val dataManager: VcsLogData, + private val changeListFiles: Collection) { + + private fun predict(candidate: FilePath, files: Collection, root: VirtualFile, history: FilesHistoryProvider): Double { + val commit = Commit(-1, System.currentTimeMillis(), dataManager.currentUser[root]?.name ?: "", changeListFiles.toSet()) + return ProbabilityProvider.predict(commit, candidate, history.getFilesHistory(root, files).toSet()) + } + + fun calculate(candidate: FilePath): Double { + val history = dataManager.index.dataGetter?.let { FilesHistoryProvider(project, dataManager, it) } ?: return 0.0 + + val roots = getGitRootFiles(project, changeListFiles) + return roots.mapNotNull { (root, files) -> + if (dataManager.index.isIndexed(root)) { + predict(candidate, files, root, history) + } + else { + null + } + }.max() ?: 0.0 + } +} + diff --git a/plugins/changeReminder/src/com/jetbrains/changeReminder/predict/PredictionProvider.kt b/plugins/changeReminder/src/com/jetbrains/changeReminder/predict/PredictionProvider.kt index cc507b462843..8df9eddadc83 100644 --- a/plugins/changeReminder/src/com/jetbrains/changeReminder/predict/PredictionProvider.kt +++ b/plugins/changeReminder/src/com/jetbrains/changeReminder/predict/PredictionProvider.kt @@ -20,76 +20,6 @@ class PredictionProvider(private val minProb: Double = 0.55) { private const val MAX_HISTORY_COMMIT_SIZE = 15 } - private class FactorCounter { - var sum = 0.0 - private set - var max = 0.0 - private set - var min = Double.MAX_VALUE - private set - - fun append(newValue: Number): FactorCounter { - return append(newValue.toDouble()) - } - - private fun append(newValue: Double): FactorCounter { - sum += newValue - max = max(max, newValue) - min = min(min, newValue) - - return this - } - } - - private fun collectFileFactors(newCommit: Commit, candidateFile: FilePath, candidateFileHistory: Set): DoubleArray { - val factors = DoubleArray(Factor.values().size) - - val timeDistanceCounter = FactorCounter() - val intersectionCounters = mutableMapOf() - val countFilesCounter = FactorCounter() - val filePrefixCounter = FactorCounter() - val pathPrefixCounter = FactorCounter() - var authorCommitted = 0.0 - - candidateFileHistory.forEach { oldCommit -> - val filesFromCommit = oldCommit.files.intersect(newCommit.files) - - countFilesCounter.append(filesFromCommit.size) - if (filesFromCommit.isNotEmpty()) { - timeDistanceCounter.append(newCommit.time - oldCommit.time) - } - - if (authorCommitted != 1.0 && oldCommit.author.startsWith(newCommit.author)) { - authorCommitted = 1.0 - } - - filesFromCommit.forEach { file -> - intersectionCounters.getOrPut(file) { FactorCounter() }.append(1) - } - } - - val candidateFilePath = candidateFile.path - val candidateFileName = candidateFile.name - newCommit.files.forEach { - pathPrefixCounter.append(StringUtil.commonPrefixLength(candidateFilePath, it.path)) - filePrefixCounter.append(StringUtil.commonPrefixLength(candidateFileName, it.name)) - } - - factors[Factor.MAX_INTERSECTION.ordinal] = intersectionCounters.values.maxBy { it.sum }?.sum ?: 0.0 - factors[Factor.SUM_INTERSECTION.ordinal] = intersectionCounters.values.sumByDouble { it.sum } - factors[Factor.MIN_DISTANCE_TIME.ordinal] = timeDistanceCounter.min - factors[Factor.COMMIT_SIZE.ordinal] = newCommit.files.size.toDouble() - factors[Factor.MAX_DISTANCE_TIME.ordinal] = timeDistanceCounter.max - factors[Factor.AVG_DISTANCE_TIME.ordinal] = timeDistanceCounter.sum / candidateFileHistory.size.toDouble() - factors[Factor.MAX_COUNT.ordinal] = countFilesCounter.max - factors[Factor.MIN_COUNT.ordinal] = countFilesCounter.min - factors[Factor.AUTHOR_COMMITTED_THE_FILE.ordinal] = authorCommitted - factors[Factor.MAX_PREFIX_PATH.ordinal] = pathPrefixCounter.max - factors[Factor.MAX_PREFIX_FILE_NAME.ordinal] = filePrefixCounter.max - - return factors - } - private fun getRelatedFiles(commit: Commit, history: Collection): Map> { val sortedHistory = history.sortedByDescending { it.time } @@ -122,7 +52,7 @@ class PredictionProvider(private val minProb: Double = 0.55) { .asSequence() .map { (candidateFile, candidateFileHistory) -> ProgressManager.checkCanceled() - val fileScore = PredictionModel.makePrediction(collectFileFactors(commit, candidateFile, candidateFileHistory)) + val fileScore = ProbabilityProvider.predict(commit, candidateFile, candidateFileHistory) candidateFile to fileScore } .filter { it.second > minProb } diff --git a/plugins/changeReminder/src/com/jetbrains/changeReminder/predict/ProbabilityProvider.kt b/plugins/changeReminder/src/com/jetbrains/changeReminder/predict/ProbabilityProvider.kt new file mode 100644 index 000000000000..043d95f452f4 --- /dev/null +++ b/plugins/changeReminder/src/com/jetbrains/changeReminder/predict/ProbabilityProvider.kt @@ -0,0 +1,85 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.changeReminder.predict + +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vcs.FilePath +import com.jetbrains.changeReminder.prediction.model.PredictionModel +import com.jetbrains.changeReminder.repository.Commit +import kotlin.math.max +import kotlin.math.min + +object ProbabilityProvider { + private class FactorCounter { + var sum = 0.0 + private set + var max = 0.0 + private set + var min = Double.MAX_VALUE + private set + + fun append(newValue: Number): FactorCounter { + return append(newValue.toDouble()) + } + + private fun append(newValue: Double): FactorCounter { + sum += newValue + max = max(max, newValue) + min = min(min, newValue) + + return this + } + } + + private fun collectFileFactors(newCommit: Commit, candidateFile: FilePath, candidateFileHistory: Set): DoubleArray { + val factors = DoubleArray(Factor.values().size) + + val timeDistanceCounter = FactorCounter() + val intersectionCounters = mutableMapOf() + val countFilesCounter = FactorCounter() + val filePrefixCounter = FactorCounter() + val pathPrefixCounter = FactorCounter() + var authorCommitted = 0.0 + + candidateFileHistory.forEach { oldCommit -> + val filesFromCommit = oldCommit.files.intersect(newCommit.files) + + countFilesCounter.append(filesFromCommit.size) + if (filesFromCommit.isNotEmpty()) { + timeDistanceCounter.append(newCommit.time - oldCommit.time) + } + + if (authorCommitted != 1.0 && oldCommit.author.startsWith(newCommit.author)) { + authorCommitted = 1.0 + } + + filesFromCommit.forEach { file -> + intersectionCounters.getOrPut(file) { FactorCounter() }.append(1) + } + } + + val candidateFilePath = candidateFile.path + val candidateFileName = candidateFile.name + newCommit.files.forEach { + pathPrefixCounter.append(StringUtil.commonPrefixLength(candidateFilePath, it.path)) + filePrefixCounter.append(StringUtil.commonPrefixLength(candidateFileName, it.name)) + } + + factors[Factor.MAX_INTERSECTION.ordinal] = intersectionCounters.values.maxBy { it.sum }?.sum ?: 0.0 + factors[Factor.SUM_INTERSECTION.ordinal] = intersectionCounters.values.sumByDouble { it.sum } + factors[Factor.MIN_DISTANCE_TIME.ordinal] = timeDistanceCounter.min + factors[Factor.COMMIT_SIZE.ordinal] = newCommit.files.size.toDouble() + factors[Factor.MAX_DISTANCE_TIME.ordinal] = timeDistanceCounter.max + factors[Factor.AVG_DISTANCE_TIME.ordinal] = timeDistanceCounter.sum / candidateFileHistory.size.toDouble() + factors[Factor.MAX_COUNT.ordinal] = countFilesCounter.max + factors[Factor.MIN_COUNT.ordinal] = countFilesCounter.min + factors[Factor.AUTHOR_COMMITTED_THE_FILE.ordinal] = authorCommitted + factors[Factor.MAX_PREFIX_PATH.ordinal] = pathPrefixCounter.max + factors[Factor.MAX_PREFIX_FILE_NAME.ordinal] = filePrefixCounter.max + + return factors + } + + fun predict(commit: Commit, candidateFile: FilePath, candidateFileHistory: Set): Double { + return PredictionModel.makePrediction(collectFileFactors(commit, candidateFile, candidateFileHistory)) + } +} \ No newline at end of file diff --git a/plugins/filePrediction/resources/META-INF/plugin.xml b/plugins/filePrediction/resources/META-INF/plugin.xml index 3ea14195a651..1eba12cf193b 100644 --- a/plugins/filePrediction/resources/META-INF/plugin.xml +++ b/plugins/filePrediction/resources/META-INF/plugin.xml @@ -6,7 +6,7 @@ com.intellij.java - com.intellij.modules.vcs + com.jetbrains.changeReminder @@ -16,7 +16,7 @@ - + diff --git a/plugins/filePrediction/src/com/intellij/filePrediction/vcs/FilePredictionVcsFeatures.kt b/plugins/filePrediction/src/com/intellij/filePrediction/vcs/FilePredictionVcsFeatures.kt index a18eec87786c..e466c0ac596d 100644 --- a/plugins/filePrediction/src/com/intellij/filePrediction/vcs/FilePredictionVcsFeatures.kt +++ b/plugins/filePrediction/src/com/intellij/filePrediction/vcs/FilePredictionVcsFeatures.kt @@ -5,8 +5,13 @@ import com.intellij.filePrediction.FilePredictionFeature import com.intellij.filePrediction.FilePredictionFeatureProvider import com.intellij.openapi.project.Project import com.intellij.openapi.vcs.ProjectLevelVcsManager +import com.intellij.openapi.vcs.actions.VcsContextFactory import com.intellij.openapi.vcs.changes.ChangeListManager import com.intellij.openapi.vfs.VirtualFile +import com.intellij.vcs.log.impl.VcsProjectLog +import com.jetbrains.changeReminder.predict.FileProbabilityRequest +import java.util.* +import kotlin.collections.HashMap class FilePredictionVcsFeatures : FilePredictionFeatureProvider { override fun getName(): String = "vcs" @@ -20,6 +25,18 @@ class FilePredictionVcsFeatures : FilePredictionFeatureProvider { result["prev_in_changelist"] = FilePredictionFeature.binary(changeListManager.isFileAffected(prevFile)) } result["in_changelist"] = FilePredictionFeature.binary(changeListManager.isFileAffected(newFile)) + + if (prevFile != null) { + val dataManager = VcsProjectLog.getInstance(project).dataManager + if (dataManager != null) { + val contextFactory = VcsContextFactory.SERVICE.getInstance() + val newPath = contextFactory.createFilePath(newFile.path, false) + + val recentFile = contextFactory.createFilePath(prevFile.path, false) + val request = FileProbabilityRequest(project, dataManager, Collections.singletonList(recentFile)) + result["related_prob"] = FilePredictionFeature.numerical(request.calculate(newPath)) + } + } return result } }