Repository navigation
IntelliJ stability update #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
38e8d2b
226e840
4f6ab88
b7cde80
e61091a
1cbd63b
bdc9c22
2266d28
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,12 @@ | |
|
|
||
| ## [Unreleased] | ||
|
|
||
| ## [0.1.0] | ||
|
|
||
| ### Changed | ||
|
|
||
| - Stability update | ||
|
|
||
| ## [0.0.12] | ||
|
|
||
| ### Changed | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,15 +5,18 @@ | |
| import com.codacy.intellij.plugin.services.api.Api | ||
| import com.codacy.intellij.plugin.services.cli.CodacyCliService | ||
| import com.codacy.intellij.plugin.services.common.Config | ||
| import com.codacy.intellij.plugin.services.common.Logger | ||
| import com.codacy.intellij.plugin.services.common.GitRemoteParser | ||
| import com.codacy.intellij.plugin.services.common.IconUtils | ||
| import com.codacy.intellij.plugin.services.git.GitProvider | ||
| import com.codacy.intellij.plugin.services.git.RepositoryManager | ||
| import com.codacy.intellij.plugin.telemetry.ExtensionInstalledEvent | ||
| import com.codacy.intellij.plugin.telemetry.Telemetry | ||
| import com.intellij.ide.plugins.DynamicPluginListener | ||
| import com.intellij.openapi.Disposable | ||
| import com.intellij.openapi.components.service | ||
| import com.intellij.openapi.project.Project | ||
| import com.intellij.openapi.util.Disposer | ||
| import com.intellij.openapi.startup.StartupActivity | ||
| import com.intellij.openapi.vfs.VirtualFileManager | ||
| import com.intellij.openapi.vfs.newvfs.BulkFileListener | ||
|
|
@@ -26,12 +29,26 @@ | |
|
|
||
| companion object { | ||
| private const val MIN_REFRESH_INTERVAL_MS = 5000L | ||
| private val activeScopes = java.util.Collections.synchronizedList(mutableListOf<CoroutineScope>()) | ||
|
|
||
| fun cancelAllScopes() { | ||
| synchronized(activeScopes) { | ||
| activeScopes.forEach { it.cancel() } | ||
| activeScopes.clear() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private var lastTriggeredTime = 0L | ||
|
|
||
| @OptIn(DelicateCoroutinesApi::class) | ||
| override fun runActivity(project: Project) { | ||
| val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) | ||
| activeScopes.add(scope) | ||
| Disposer.register(project, Disposable { | ||
| scope.cancel() | ||
| activeScopes.remove(scope) | ||
| }) | ||
|
|
||
| // Check for first installation | ||
| val config = Config.instance | ||
| if (config.state.isFirstRun) { | ||
|
|
@@ -50,7 +67,7 @@ | |
| connection.subscribe(GitRepository.GIT_REPO_CHANGE, GitRepositoryChangeListener { repository -> | ||
|
|
||
|
|
||
| val gitResult = initializeGit(project) | ||
| val gitResult = initializeGit(project, scope) | ||
| val repositoryManager = gitResult.first | ||
| val gitInfo = gitResult.second | ||
|
|
||
|
|
@@ -60,19 +77,30 @@ | |
|
|
||
| AiAgentService.getService(project) | ||
|
|
||
| GlobalScope.launch { | ||
| Api().listTools() | ||
| scope.launch { | ||
| try { | ||
| service<Api>().listTools() | ||
| } catch (e: Exception) { | ||
|
Check warning on line 83 in src/main/kotlin/com/codacy/intellij/plugin/listeners/StartupListener.kt
|
||
| Logger.warn("Failed to load tools: ${e.message}") | ||
| } | ||
| } | ||
|
|
||
| if (repository.currentBranch != null) { | ||
| CoroutineScope(Dispatchers.Default).launch { | ||
| scope.launch { | ||
| repositoryManager.handleStateChange() | ||
| } | ||
| } else { | ||
| repositoryManager.notifyDidChangeConfig() | ||
| } | ||
| }) | ||
|
|
||
| // Perform initial startup initialization without waiting for a git change event | ||
| val gitRepository = GitProvider.getRepository(project) | ||
| if (gitRepository != null) { | ||
| val repositoryManager = project.service<RepositoryManager>() | ||
| scope.launch { repositoryManager.open(gitRepository) } | ||
| } | ||
|
|
||
| connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener { | ||
| private fun handle(events: List<VFileEvent>) { | ||
| val now = System.currentTimeMillis() | ||
|
|
@@ -95,12 +123,11 @@ | |
| }) | ||
| } | ||
|
|
||
| @OptIn(DelicateCoroutinesApi::class) | ||
| private fun initializeGit(project: Project): Pair<RepositoryManager, GitRemoteParser.GitRemoteInfo> { | ||
| private fun initializeGit(project: Project, scope: CoroutineScope): Pair<RepositoryManager, GitRemoteParser.GitRemoteInfo> { | ||
| val gitRepository = GitProvider.getRepository(project) | ||
| val repositoryManager = project.service<RepositoryManager>() | ||
| if (gitRepository != null && repositoryManager.currentRepository != gitRepository) | ||
| GlobalScope.launch { repositoryManager.open(gitRepository) } | ||
| scope.launch { repositoryManager.open(gitRepository) } | ||
|
|
||
| val remote = gitRepository?.remotes?.firstOrNull() | ||
| ?: throw IllegalStateException("No remote found in the Git repository") | ||
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,7 @@ | |
| import java.net.HttpURLConnection | ||
| import java.net.URL | ||
|
|
||
| @Service | ||
| @Service(Service.Level.APP) | ||
| class Api { | ||
|
|
||
| private val config = service<Config>() | ||
|
|
@@ -47,15 +47,13 @@ | |
| Gson().fromJson(response, responseClass) | ||
| } | ||
| } else { | ||
| Logger.error("Failed to fetch data: HTTP response code $responseCode") | ||
| if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) | ||
| config.storeApiToken("") | ||
| throw Exception("Failed to fetch data: HTTP response code $responseCode") | ||
| } | ||
|
Comment on lines
49
to
53
|
||
| } | ||
| catch (e: Exception) { | ||
|
lventura-codacy marked this conversation as resolved.
|
||
| Logger.error("Failed to fetch data: ${e.message}") | ||
| responseClass.newInstance() | ||
| throw Exception("Failed to fetch data: ${e.message}", e) | ||
|
Check warning on line 56 in src/main/kotlin/com/codacy/intellij/plugin/services/api/Api.kt
|
||
| } | ||
|
lventura-codacy marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
@@ -131,7 +129,7 @@ | |
| return try { | ||
| makeRequest(endpointUrl, GetUserProfileResponse::class.java).data | ||
| } catch (e: Exception) { | ||
| Logger.error("Failed to fetch user profile: ${e.message}") | ||
| Logger.warn("Failed to fetch user profile: ${e.message}") | ||
| null | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ import com.codacy.intellij.plugin.services.git.RepositoryManager.RepositoryManag | |
| import kotlinx.coroutines.* | ||
| import com.intellij.notification.* | ||
| import com.intellij.openapi.components.Service | ||
| import com.intellij.openapi.components.service | ||
| import com.intellij.openapi.fileEditor.FileEditorManager | ||
| import com.intellij.openapi.progress.ProgressManager | ||
| import com.intellij.openapi.progress.Task | ||
|
|
@@ -32,7 +33,7 @@ class PullRequest( | |
| var files: List<FileDetails> = mutableListOf() | ||
| var gates: QualitySettingsData? = null | ||
| private val refreshTimeout: TimeoutManager = TimeoutManager() | ||
| private val api = Api() | ||
| private val api = service<Api>() | ||
| private var repositoryManager: RepositoryManager? = null | ||
|
Comment on lines
35
to
37
|
||
|
|
||
| fun init( | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -19,12 +19,14 @@ | |||||||||||||||
| import com.intellij.openapi.project.Project | ||||||||||||||||
| import git4idea.repo.GitRepository | ||||||||||||||||
| import kotlinx.coroutines.* | ||||||||||||||||
| import kotlinx.coroutines.sync.Mutex | ||||||||||||||||
| import kotlinx.coroutines.sync.withLock | ||||||||||||||||
|
|
||||||||||||||||
| const val LOAD_RETRY_TIME: Long = 2 * 60 * 1000 | ||||||||||||||||
| const val MAX_LOAD_ATTEMPTS: Int = 5 | ||||||||||||||||
|
|
||||||||||||||||
| @Service(Service.Level.PROJECT) | ||||||||||||||||
| class RepositoryManager(private val project: Project) { | ||||||||||||||||
| class RepositoryManager(private val project: Project) : Disposable { | ||||||||||||||||
|
Check warning on line 29 in src/main/kotlin/com/codacy/intellij/plugin/services/git/RepositoryManager.kt
|
||||||||||||||||
|
|
||||||||||||||||
| enum class RepositoryManagerState { | ||||||||||||||||
| Initializing, | ||||||||||||||||
|
|
@@ -51,32 +53,38 @@ | |||||||||||||||
| private var loadAttempts: Int = 0 | ||||||||||||||||
| private val loadTimeout: TimeoutManager = TimeoutManager() | ||||||||||||||||
| private val refreshTimeout: TimeoutManager = TimeoutManager() | ||||||||||||||||
| private val api = Api() | ||||||||||||||||
| private val config = Config() | ||||||||||||||||
| private val api = service<Api>() | ||||||||||||||||
| private val config = Config.instance | ||||||||||||||||
| private var pullRequestInstance: PullRequest? = null | ||||||||||||||||
|
|
||||||||||||||||
| // Branch state management | ||||||||||||||||
| private var enabledBranches: List<Branch> = emptyList() | ||||||||||||||||
| private var branchState: BranchState = BranchState.OnUnknownBranch | ||||||||||||||||
| private var lastBranchState: BranchState? = null | ||||||||||||||||
|
|
||||||||||||||||
| private val serviceScope = CoroutineScope(Dispatchers.Default + SupervisorJob()) | ||||||||||||||||
| private val openMutex = Mutex() | ||||||||||||||||
|
|
||||||||||||||||
| private var onDidUpdatePullRequestListeners = mutableListOf<() -> Unit>() | ||||||||||||||||
| private var onDidLoadRepositoryListeners = mutableListOf<() -> Unit>() | ||||||||||||||||
| private var onDidChangeStateListeners = mutableListOf<() -> Unit>() | ||||||||||||||||
|
|
||||||||||||||||
| @OptIn(DelicateCoroutinesApi::class) | ||||||||||||||||
| suspend fun open(gitRepository: GitRepository) { | ||||||||||||||||
| config.init() | ||||||||||||||||
| if (config.storedApiToken.isNullOrBlank()) { | ||||||||||||||||
| setNewState(RepositoryManagerState.NeedsAuthentication) | ||||||||||||||||
| return | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| if (currentRepository != gitRepository) { | ||||||||||||||||
| if (openMutex.withLock { | ||||||||||||||||
| if (currentRepository != gitRepository) { | ||||||||||||||||
| currentRepository = gitRepository | ||||||||||||||||
| true | ||||||||||||||||
| } else false | ||||||||||||||||
| }) { | ||||||||||||||||
| ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Opening repository", false) { | ||||||||||||||||
| override fun run(indicator: com.intellij.openapi.progress.ProgressIndicator) { | ||||||||||||||||
| GlobalScope.launch { | ||||||||||||||||
| currentRepository = gitRepository | ||||||||||||||||
| serviceScope.launch { | ||||||||||||||||
|
||||||||||||||||
| serviceScope.launch { | |
| serviceScope.launch { | |
| // Bail out early if a newer repository has been opened while this job was queued/running | |
| if (currentRepository != gitRepository) { | |
| Logger.info("Repository changed during open; aborting outdated job") | |
| return@launch | |
| } |
Copilot
AI
Mar 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The catch (e: Exception) around the whole open() body will also catch API/network failures from api.getRepository(...), but the log message/state update treats them as a Git remote parsing problem (NoRepository). Split the try/catch so remote parsing errors are handled separately from API errors, and set a more accurate state/log message for request failures (or let them propagate to a higher-level handler).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
initializeGit(project, scope)can throw (e.g., when no Git remote is configured). Since this call is inside the repository change listener without a try/catch, an exception here can abort the listener and spam IDE logs. Wrap this initialization in error handling and fall back to a safe state (e.g., skip starting services / setRepositoryManagertoNoGitRepository).