diff --git a/CHANGELOG.md b/CHANGELOG.md index 429d9fb..343e150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2026.1.2] + +### Fixes + +- Fixed [multiple "Collecting Git Scope" tasks slow down PHPStorm](https://github.com/comod/git-scope-pro/issues/92) +- Fixed one instance of illegal method call on EDT + ## [2026.1.1] ### Fixes diff --git a/gradle.properties b/gradle.properties index 8b3a441..676aaf1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ pluginGroup=org.woelkit.plugins pluginName=Git Scope pluginRepositoryUrl=https://github.com/comod/git-scope-pro -pluginVersion=2026.1.1 +pluginVersion=2026.1.2 pluginSinceBuild=243 platformType=IU diff --git a/src/main/java/implementation/compare/ChangesService.java b/src/main/java/implementation/compare/ChangesService.java index a98ed50..23901f8 100644 --- a/src/main/java/implementation/compare/ChangesService.java +++ b/src/main/java/implementation/compare/ChangesService.java @@ -29,6 +29,8 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; public class ChangesService extends GitCompareWithRefAction implements Disposable { @@ -58,6 +60,8 @@ public record ChangesResult(Collection mergedChanges, Collection private final GitService git; private Task.Backgroundable task; private final AtomicBoolean disposing = new AtomicBoolean(false); + private final AtomicReference currentIndicator = new AtomicReference<>(); + private final AtomicLong collectionGeneration = new AtomicLong(0); public ChangesService(Project project) { this.project = project; @@ -85,6 +89,7 @@ public void collectChangesWithCallback(TargetBranchMap targetBranchByRepo, Consu // Capture the current project reference to ensure consistency final Project currentProject = this.project; final GitService currentGitService = this.git; + final long gen = collectionGeneration.incrementAndGet(); task = new Task.Backgroundable(currentProject, "Collecting " + Defs.APPLICATION_NAME, true) { @@ -92,8 +97,10 @@ public void collectChangesWithCallback(TargetBranchMap targetBranchByRepo, Consu @Override public void run(@NotNull ProgressIndicator indicator) { - // Early exit if disposing - if (disposing.get() || indicator.isCanceled()) { + currentIndicator.set(indicator); + try { + // Early exit if disposing or superseded by a newer collection request + if (disposing.get() || indicator.isCanceled() || collectionGeneration.get() != gen) { return; } @@ -110,6 +117,7 @@ public void run(@NotNull ProgressIndicator indicator) { } repositories.forEach(repo -> { + if (indicator.isCanceled() || collectionGeneration.get() != gen) return; try { String branchToCompare = getBranchToCompare(targetBranchByRepo, repo); @@ -177,6 +185,9 @@ public void run(@NotNull ProgressIndicator indicator) { } else { result = new ChangesResult(_changes, _scopeChanges, _localChanges); } + } finally { + currentIndicator.compareAndSet(indicator, null); + } } @Override @@ -184,7 +195,7 @@ public void onSuccess() { // Ensure result is accessed only on the UI thread to update the UI component ApplicationManager.getApplication().invokeLater(() -> { // Double-check the project is still valid - if (!currentProject.isDisposed() && callBack != null) { + if (!currentProject.isDisposed() && callBack != null && this.result != null) { callBack.accept(this.result); } }, ModalityState.defaultModalityState(), __ -> disposing.get()); @@ -199,6 +210,10 @@ public void onThrowable(@NotNull Throwable error) { }, ModalityState.defaultModalityState(), __ -> disposing.get()); } }; + ProgressIndicator prev = currentIndicator.getAndSet(null); + if (prev != null) { + prev.cancel(); + } task.queue(); } diff --git a/src/main/java/implementation/lineStatusTracker/MyLineStatusTrackerImpl.java b/src/main/java/implementation/lineStatusTracker/MyLineStatusTrackerImpl.java index 6c660e1..846141e 100644 --- a/src/main/java/implementation/lineStatusTracker/MyLineStatusTrackerImpl.java +++ b/src/main/java/implementation/lineStatusTracker/MyLineStatusTrackerImpl.java @@ -21,6 +21,7 @@ import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.concurrency.SequentialTaskExecutor; import com.intellij.util.messages.MessageBus; import com.intellij.util.messages.MessageBusConnection; import implementation.gutter.Range; @@ -32,7 +33,9 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; /** * Manages custom line status markers for scope changes in editor gutters. @@ -44,6 +47,9 @@ public class MyLineStatusTrackerImpl implements Disposable { private final Project project; private MessageBusConnection messageBusConnection; private final AtomicBoolean disposing = new AtomicBoolean(false); + private final ExecutorService updateExecutor = + SequentialTaskExecutor.createSequentialApplicationPoolExecutor("ScopeGutterUpdate"); + private final AtomicLong updateGeneration = new AtomicLong(0); // Lightweight disposable token to check disposal state without capturing 'this' private static class DisposalToken { @@ -70,6 +76,7 @@ private static final class RendererInfo { @Override public void dispose() { disposalToken.disposed = true; + updateExecutor.shutdownNow(); releaseAll(); } @@ -118,9 +125,11 @@ public void update(Map scopeChangesMap, Map loca } final DisposalToken token = this.disposalToken; - // Process editors in parallel on background threads - ApplicationManager.getApplication().executeOnPooledThread(() -> { - if (token.disposed) return; + final long gen = updateGeneration.incrementAndGet(); + + updateExecutor.execute(() -> { + // Drop stale update if a newer one has been submitted while we were queued + if (token.disposed || updateGeneration.get() != gen) return; Editor[] editors = EditorFactory.getInstance().getAllEditors(); diff --git a/src/main/java/service/ViewService.java b/src/main/java/service/ViewService.java index 1a1f84d..889bacc 100644 --- a/src/main/java/service/ViewService.java +++ b/src/main/java/service/ViewService.java @@ -84,7 +84,17 @@ private static class DisposalToken { public ViewService(Project project) { this.project = project; - EventQueue.invokeLater(this::initDependencies); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + // Force lazy service initialization on BGT where blocking ops are allowed, + // preventing EDT warnings from config loading (e.g. Maven path macro resolution) + project.getService(ChangesService.class); + project.getService(ToolWindowServiceInterface.class); + project.getService(StatusBarService.class); + project.getService(GitService.class); + project.getService(TargetBranchService.class); + project.getService(State.class); + EventQueue.invokeLater(ViewService.this::initDependencies); + }); } private final @NotNull ExecutorService changesExecutor =